写点什么

探讨 JS 对象如何缓存属性的值

用户头像
零维
关注
发布于: 2021 年 04 月 28 日

我们有一个这样的对象:


const foo = {  expensiveOperator() {    // 我将会花费很多时间来返回结果    this.cache = computeResult();    return this.cache;  }}
复制代码


如果我们的业务中需要调用 foo.expensiveOperator 很多次,并且每次调用都需要耗费很多时间。这个时候我们就在想,能不能临时把这个结果缓存起来?今天我们就来讨论解决这个问题的方案。


最直接的,我们可以在此对象上定义一个 'cache' 属性,来存储我们的结果:


const foo = {  cache: null,  expensiveOperator() {    if (this.cache) {      return this.cache;    }        // 我将会花费很多时间来返回结果    this.cache = computeResult();    return this.cache;  }}
复制代码


使用这种模式,我们已经可以避免频繁操作这个昂贵的操作了。


但是还是有个缺陷,我们外部可以随便的修改 foo.cache 的值,并不是很安全,我们希望 foo.cache 只是一个「私有属性」,只能被 foo.expensiveOperator 修改。但是 JS 的对象并不能设置私有属性,这时候我们就需要使用 Module Pattern 这个设计模式来进行封装。


其实很简单, 你大概率也见过这个设计模式,它最开始因为 Douglas Crockford 的宣传而受到关注的,在 《JavaScript 语言精粹》里面也有介绍( P40 页 )。


const foo = (function(){  let cache = null;
return { expensiveOperator() { if (cache) { return cache; } // 我将会花费很多时间来返回结果 cache = computeResult(); return cache; } }})()
复制代码


上面的也可以用 Class 的形式来做,但就我个人喜好而言,我并不是很喜欢 JS 的 Class 写法,我觉得它很丑陋,我更倾心于使用对象字面量(object literal notation)的形式。当然了,我没有说 Class 的写法不好,毕竟它让烦人的原型消失了:)


接下来我们看能否继续改进。


如果只写一个还好,如果我们的 foo 对象里面很多操作都是需要缓存的,那我们要针对每个操作都定义一个变量来存储缓存的值,并且还要每次都要添加判断缓存是否存在的逻辑,太麻烦了。我在项目中也是使用的这种结构,当缓存多了的时候,我明显感觉到了混乱和对未来添加更多缓存的力不从心。


下面我们来新介绍一个新的模式来解决以上的问题,也是我目前认为解决这个问题最优的方案。


首先我们定义一个它的 get 属性 ,然后呢,在第一次获取值之后使用 Object.defineProperty API 把这个值写入这个对象。代码如下所示:


const foo = {  get expensiveOperator() {    // 很耗时的操作    const actualData = computeResult();
Object.defineProperty(this, "expensiveOperator", { value: actualData, writable: false, configurable: false, enumerable: false });
return actualData; }}
复制代码


使用这个结构,我们并不需要临时变量来存缓存,我们也设置了这个属性为不可修改,防止存储的值被篡改,可能你觉得代码量很多,但是我们 defineProperty 的过程可以封装成一个函数,比如下面这样:


const foo = {  get expensiveOperator() {    // 很耗时的操作    const actualData = computeResult();
defineProperty.call(this, 'expensiveOperator', actualData);
return actualData; }}
function defineProperty(propertyName, value) { Object.defineProperty(this, propertyName, { value, writable: false, configurable: false, enumerable: false });}
复制代码


不过我也发现了一个缺点,那就是在这个对象存在的时候,我们想修改 expensiveOperator 存在的值得时候就不那么方便了,只好让这个对象重新生成了。但是还是不得不感叹,这种方法还是太妙了。


上面这种情况比较适合在对象的生命周期内,缓存的属性值不会发生变化的情况,对我来说,是非常适合的。


做缓存是提高我们软件性能最简单粗暴且有效的方式了,但是在具体业务场景下,每个方案各有利弊,最后还是要看情况讨论。

发布于: 2021 年 04 月 28 日阅读数: 23
用户头像

零维

关注

还未添加个人签名 2018.09.17 加入

还未添加个人简介

评论

发布
暂无评论
探讨 JS 对象如何缓存属性的值