我正在使用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', '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 = this.log;
},
};
console.log(obj.latest);
// expected output: "c"
console.log(obj.notifier); // Array ["a", "b", "c"]
console.log(obj.notifier); // Array ["a", "b", "c"]
我想我不确定为什么需要先删除该属性,delete this.notifier;
? 这不会每次都使缓存失效吗?
更新 2: @Bergi,感谢您对示例代码的建议修改。
我运行了这个(delete
):
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 = console.log("heavy computation");
},
};
console.log(obj.latest);
// expected output: "c"
obj.notifier;
obj.notifier;
并得到:
> "c"
> "heavy computation"
我运行了这个(没有delete
):
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 = 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,或随之而来的所有额外内容;this.prop = ...
重新创建属性,这次更像是我们习惯的"正常"属性,因此下次访问它时不会经过getter。
所以事实上,MDN 上的例子演示了两者:
- "惰性获取者":它只会在需要值时计算值;
- 和"记忆":它只会计算一次,然后每次都返回相同的结果。
对象属性的深入解释
因此,您可以更好地理解当我们首先声明我们的对象,我们的getter,当我们删除然后重新分配属性时会发生什么,我将尝试更深入地了解对象属性是什么。让我们举一个基本的例子:
const obj = {
prop: 2
}
在这种情况下,我们可以使用 getOwnPropertyDescriptor 获取此属性的"配置":
console.log(Object.getOwnPropertyDescriptor(obj, 'prop'))
哪些输出
{
configurable: true,
enumerable: true,
value: 2,
writable: true,
}
事实上,如果我们想不必要地明确它,我们可以用 definePropertyobj = { prop: 2 }
另一种(等效但冗长)的方式来定义我们的:
const obj = {}
Object.defineProperty(obj, 'prop', {
configurable: true,
enumerable: true,
value: 2,
writable: true,
})
现在,当我们使用 getter 定义我们的属性时,它相当于像这样定义它:
Object.defineProperty(obj, 'prop', {
configurable: true,
enumerable: true,
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, 'prop', {
enumerable: true,
configurable: true,
get() {
const finalValue = Math.random()
Object.defineProperty(obj, 'prop', {
enumerable: true,
configurable: true,
writable: true,
value: finalValue,
})
return finalValue
},
})
奖金回合
有趣的事实:在JS中,将undefined
分配给我们想要"删除"的对象属性(或假装它根本不存在)是一种常见的模式。甚至还有一种新的语法可以帮助解决这个问题,称为"Nullish 合并运算符"(??
):
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
的 s/setter
的记忆行为,而是在使用getter
s/setter
s 的同时实现记忆。
正如文章所述:
请注意,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
语句对我来说似乎很奇怪。
希望有帮助