Mozilla JS getter文档示例如何体现lazy/memoized懒加载缓存特性?
核心原理说明
JS 访问器属性的执行逻辑
首先明确 JS 中对象属性的两种类型:
- 访问器属性:通过
get/set语法定义,读取/赋值时会执行对应的函数,本身不存储实际值 - 数据属性:普通的键值对属性,读取时直接返回存储的静态值
你在对象字面量中定义的get notifier()本质是给对象添加了一个访问器属性,整个 memoization 流程如下:
- 首次读取
obj.notifier时,JS 匹配到该访问器属性,触发对应的 getter 函数执行 delete this.notifier执行:此时删除的是这个原本存在的访问器属性,执行完成后对象上已经没有notifier相关的任何属性return this.notifier = 计算结果执行:由于对象上已经不存在notifier属性,JS 会自动给对象新增一个数据属性,存储计算结果,之后直接返回该值- 后续再读取
obj.notifier时,JS 会直接匹配到这个新增的数据属性,直接返回存储的值,不会再触发原来的 getter 函数,自然也不会再执行delete和计算逻辑,实现了只计算一次的缓存效果。
你可以通过Object.getOwnPropertyDescriptor(obj, 'notifier')分别打印首次读取notifier前后的属性描述符,就能直观看到属性从访问器属性变成数据属性的变化。
认知误区解释
你直觉认为delete会清空缓存,是搞反了执行顺序:delete只在第一次触发 getter的时候执行,删除的是旧的访问器属性,后续 getter 根本不会被调用,delete自然也不会再执行,不存在每次清空缓存的情况。
不带delete时重复计算的原因
如果删掉delete语句:
- 你在 getter 中执行
this.notifier = 计算结果时,对象上的notifier访问器属性仍然存在 - 给访问器属性赋值时,如果没有定义对应的
set方法,赋值操作会直接被忽略,对象上永远不会生成存储值的数据属性 - 所以每次读取
obj.notifier都会触发 getter 执行,导致计算逻辑重复运行。
和 Ruby memoization 逻辑的对比
Ruby 的@property ||= calc()是通过额外的实例变量存储缓存值,属于额外加了一层缓存判断;而 JS 的这个实现是直接把「需要执行计算的访问器属性」替换成「直接返回值的数据属性」,两种实现逻辑不同,但最终效果一致,都是只在首次读取时执行计算,后续直接返回缓存结果。
内容的提问来源于stack exchange,提问作者Zhao Li
相关产品推荐
相关产品推荐

