问题描述
我正在使用 Mozilla 的文档阅读 JavaScript 的虚拟 getter:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
在下面的例子中,对象有一个 getter 作为它自己的属性。 获取该属性后,该属性将从对象中删除并 重新添加,但这次隐式作为数据属性。最后, 返回值。
get notifier() {
delete this.notifier;
return this.notifier = document.getElementById('bookmarked-notification-anchor');
},
这个例子是在文章讨论惰性/智能/记忆化之后出现的,但我没有看到代码是如何成为惰性/智能/记忆化getter的例子。
或者那部分完全是在谈论别的东西?
感觉好像我没有跟上文章的流程,可能是因为我不理解某些关键概念。
请告诉我,如果我只是过度思考这一点而该部分只是硬塞进来,或者该部分是否确实以某种方式与懒惰/智能/记忆有关。
谢谢您的指导??
更新 1:
我想也许我不知道如何验证代码是否被记住了。
我尝试在页面上的 IDE 中运行它:
const obj = {
log: ['a','b','c'],get latest() {
if (this.log.length === 0) {
return undefined;
}
return this.log[this.log.length - 1];
},get notifier() {
delete this.notifier;
return this.notifier = document.getElementById('bookmarked-notification-anchor');
},};
console.log(obj.latest);
// expected output: "c"
console.log(obj.notifier); // returns null
这似乎更合适,但我无法验证是否正在使用缓存:
const obj = {
log: ['a',get notifier() {
delete this.notifier;
return this.notifier = this.log;
},};
console.log(obj.latest);
// expected output: "c"
console.log(obj.notifier); // Array ["a","b","c"]
console.log(obj.notifier); // Array ["a","c"]
我想我不确定为什么需要先删除属性,delete this.notifier;
?这不会每次都使缓存失效吗?
我运行了这个(使用 delete
):
const obj = {
log: ['a',get notifier() {
delete this.notifier;
return this.notifier = console.log("heavy computation");
},};
console.log(obj.latest);
// expected output: "c"
obj.notifier;
obj.notifier;
并得到:
> "c"
> "heavy computation"
我运行了这个(没有 delete
):
const obj = {
log: ['a',get notifier() {
//delete this.notifier;
return this.notifier = console.log("heavy computation");
},};
console.log(obj.latest);
// expected output: "c"
obj.notifier;
obj.notifier;
并得到:
> "c"
> "heavy computation"
> "heavy computation"
所以这肯定证明正在发生记忆化。也许有太多的帖子复制和粘贴更新,但我很难理解为什么 delete
是必要的记忆。我分散而天真的大脑认为代码应该在没有 delete
时记忆。
抱歉,我需要坐下来考虑一下。除非您有关于如何理解正在发生的事情的快速提示。
再次感谢大家的帮助??
更新 3:
我想我仍然缺少一些东西。
从 Ruby 开始,我将记忆化理解为:
如果它存在/预先计算,则使用它;如果不存在,则计算它
类似的东西:this.property = this.property || this.calc()
使用示例代码片段中的 delete
时,该属性是不是总是不存在,因此总是需要重新计算?
我的逻辑肯定有问题,但我没有看到。我想这可能是“我不知道我不知道什么”的情景。
解决方法
如何测试记忆化
测试某些内容是否被记住的简单方法是使用 Math.random()
:
const obj = {
get prop() {
delete this.prop
return this.prop = Math.random()
}
}
console.log(obj.prop) // 0.1747926550503922
console.log(obj.prop) // 0.1747926550503922
console.log(obj.prop) // 0.1747926550503922
如果 obj.prop
没有被记住,它每次都会返回一个随机数:
const obj = {
get prop() {
return Math.random()
}
}
console.log(obj.prop) // 0.7592929509653794
console.log(obj.prop) // 0.33531447188307895
console.log(obj.prop) // 0.685061719658401
它是如何工作的
在第一个例子中发生的是
-
delete
删除属性定义,包括 getter(当前正在执行的函数)和任何 setter,或者 all the extra stuff 连同它; -
this.prop = ...
重新创建属性,这次更像是我们习惯的“普通”属性,因此下次访问它时不会通过 getter。
确实,MDN 上的示例演示了两者:
- 一个“懒惰的getter”:它只会在需要该值时计算该值;
- 和“记忆化”:它只会计算一次,然后每次都返回相同的结果。
深入讲解对象属性
所以你可以更好地理解当我们首先声明我们的对象、我们的 getter 时会发生什么,当我们删除时,然后我们重新分配属性,我将尝试更深入地了解对象的属性是。让我们举一个基本的例子:
const obj = {
prop: 2
}
在这种情况下,我们可以通过 getOwnPropertyDescriptor 获取此属性的“配置”:
console.log(Object.getOwnPropertyDescriptor(obj,'prop'))
哪个输出
{
configurable: true,enumerable: true,value: 2,writable: true,}
事实上,如果我们想对其进行不必要的明确,我们可以用 defineProperty 定义我们的 obj = { prop: 2 }
另一个(等效但冗长的)方式:
const obj = {}
Object.defineProperty(obj,'prop',{
configurable: true,})
现在,当我们用一个 getter 来定义我们的属性时,它相当于像这样定义它:
Object.defineProperty(obj,get() {
delete obj.prop
return obj.prop = Math.random()
}
})
当我们执行 delete this.prop
时,它会删除整个定义。事实上:
console.log(obj.prop) // 2
delete obj.prop
console.log(Object.getOwnPropertyDescriptor(obj,'prop')) // undefined
最后,this.prop = ...
重新定义了刚刚删除的属性。这是defineProperty
的俄罗斯娃娃。
以下是所有完全不必要的显式定义的样子:
const obj = {}
Object.defineProperty(obj,{
enumerable: true,configurable: true,get() {
const finalValue = Math.random()
Object.defineProperty(obj,{
enumerable: true,value: finalValue,})
return finalValue
},})
奖金回合
有趣的事实:将 undefined
分配给我们想要“删除”的对象属性是 JS 中的一种常见模式(或假装它根本不存在)。甚至还有一种新的语法可以帮助解决这个问题,称为 "Nullish coalescing operator" (??
):
const obj = {}
obj.prop = 0
console.log(obj.prop) // 0
console.log(obj.prop ?? 2) // 0
obj.prop = undefined
console.log(obj.prop) // undefined
console.log(obj.prop ?? 2) // 2
但是,我们仍然可以“检测”该属性在分配 undefined
时是否存在。只有 delete
才能真正将其从对象中移除:
const obj = {}
console.log(obj.prop) // undefined
console.log(obj.hasOwnProperty('prop')) // false
console.log(Object.getOwnPropertyDescriptor(obj,'prop')) // undefined
obj.prop = undefined
console.log(obj.prop) // undefined
console.log(obj.hasOwnProperty('prop')) // true
console.log(Object.getOwnPropertyDescriptor(obj,'prop')) // {"writable":true,"enumerable":true,"configurable":true}
delete obj.prop
console.log(obj.prop) // undefined
console.log(obj.hasOwnProperty('prop')) // false
console.log(Object.getOwnPropertyDescriptor(obj,'prop')) // undefined
,
@sheraff 的回答让我有了以下理解。希望@sheraff 的这种重新措辞对其他人有用。
来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get的代码:
get notifier() {
delete this.notifier;
return this.notifier = document.getElementById('bookmarked-notification-anchor');
},
不是在演示 getter
的/setter
的记忆行为,而是在使用 getter
s/setter
的同时实现记忆。
正如文章所述:
请注意,getter 本质上不是“懒惰”或“记忆化”的;你必须 如果您需要这种行为,请实施此技术。
以下示例代码片段帮助我意识到了这一点。
这是基于文章中提供的示例代码的工作片段:
const obj = {
get notifier() {
console.log("getter called");
delete this.notifier;
return this.notifier = Math.random();
},};
console.log(obj.notifier);
console.log(obj.notifier);
// results in
> "getter called"
> 0.644950142066832
> 0.644950142066832
这是一个更详细的代码版本,它简化了一些 JavaScript 技巧,因此我更容易理解(@sheraff 的回答帮助我获得了对此代码的评论):
const obj = {
get notifier() {
console.log("getter called");
delete this.notifier; // remove this getter function from the object so Math.random() won't be called again
let value = Math.random(); // this resource intensive operation will be skipped on subsequent calls to obj.notifier
this.notifier = value; // create a new property of the same name that does not have getter/setter
return this.notifier; // return new property so the first call to obj.notifier does not return undefined
},};
console.log(obj.notifier);
console.log(obj.notifier);
// results in
> "getter called"
> 0.7212959093641651
> 0.7212959093641651
如果我从未阅读过这篇文章并且来自另一种编程语言,我将如何实现记忆化:
const obj = {
get notifier() {
if (this._notifier) {
return this._notifier;
} else {
console.log("calculation performed");
return this._notifier = Math.random();
}
},};
console.log(obj.notifier);
console.log(obj.notifier);
// results in
> "calculation performed"
> 0.6209661598889056
> 0.6209661598889056
可能存在我不知道的低效率和其他原因,这应该阻止以这种方式实现记忆,但我理解它很简单明了。这是我在实现记忆化的文章中期望看到的。没看到,不知为何以为这篇文章只是在演示记忆。
以上所有代码片段都实现了记忆化,这是文章中示例代码的意图。我最初的困惑是误读了代码演示了记忆,这让我觉得 delete
语句很奇怪。
希望有帮助??