我的问题是:箭头函数是否仍然在词法上绑定到这个,只要其他人还活着就保持 Foo 活着,或者在这种情况下 Foo 可能会被垃圾收集?
就规范而言,箭头函数引用了创建它的环境对象,并且该环境对象具有this
, 并且this
引用Foo
了由该调用创建的实例。因此,任何依赖于Foo
未保存在内存中的代码都依赖于优化,而不是指定的行为。
再优化,归结为你使用的JavaScript引擎是否对闭包进行了优化,具体情况下能否对闭包进行优化。(有很多事情可以阻止它。)就像这个带有传统函数的 ES5 示例一样:
function Foo(other) {
var t = this;
other.callback = function() { };
}
在这种情况下,函数关闭包含 的上下文t
,因此理论上,有一个引用,t
而该引用又将Foo
实例保存在内存中。
这就是理论,但在实践中,现代 JavaScript 引擎可以看到t
闭包没有使用它,并且可以对其进行优化,前提是这样做不会引入可观察到的副作用。是否会,如果会,何时会,完全取决于引擎。
由于箭头函数确实是词法闭包,因此情况完全相似,因此您希望 JavaScript 引擎做同样的事情:优化它,除非它会导致可以观察到的副作用。也就是说,请记住箭头函数是非常新的,所以很可能引擎还没有对此进行太多优化(没有双关语)。
在这种特殊情况下,我在 2016 年 3 月(在 Chrome v48.0.2564.116 64 位中)和 2021 年 1 月(Brave v1.19.86 基于 Chromium v88.0.4324)编写此答案时使用的 V8 版本。 96)确实优化了关闭。如果我运行这个:
"use strict";
function Foo(other) {
other.callback = () => this; // <== Note the use of `this` as the return value
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n]);
}
// Let's keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({});
log("Done, check the heap");
function log(msg) {
let p = document.createElement('p');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
然后在 devtools 中拍摄堆快照,我看到预期的 10,001 个Foo
内存实例。如果我运行垃圾收集(现在你可以使用垃圾桶图标;在早期版本中,我必须使用特殊标志运行,然后调用gc()
函数),我仍然会看到 10,001个Foo
实例:

但是如果我改变回调所以它没有引用this
:
other.callback = () => { }; // <== No more `this`
"use strict";
function Foo(other) {
other.callback = () => {}; // <== No more `this`
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n]);
}
// Let's keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({});
log("Done, check the heap");
function log(msg) {
let p = document.createElement('p');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
并再次运行该页面,我什至不必强制垃圾收集,Foo
内存中只有一个实例(我放在那里以便在快照中轻松找到的那个):

我想知道是不是因为回调是完全空的这一事实才允许优化,并且惊喜地发现它不是:Chrome 很高兴在放开 时保留部分闭包this
,如下所示:
"use strict";
function Foo(other, x) {
other.callback = () => x * 2;
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n], n);
}
// Let's keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({}, 0);
document.getElementById("btn-call").onclick = function() {
let r = Math.floor(Math.random() * a.length);
log(`a[${r}].callback(): ${a[r].callback()}`);
};
log("Done, click the button to use the callbacks");
function log(msg) {
let p = document.createElement('p');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
<input type="button" id="btn-call" value="Call random callback">
尽管回调存在并且引用了x
,但 Chrome 还是优化了Foo
实例。
您询问了有关如何this
在箭头函数中解析的规范参考:该机制遍布整个规范。每个环境(例如调用函数创建的环境)都有一个[[thisBindingStatus]]
内部槽,"lexical"
用于箭头函数。在确定 的值时this
,使用内部操作ResolveThisBinding
,它使用内部GetThisEnviroment
操作来查找已this
定义的环境。当进行“正常”函数调用时,如果环境不是环境,BindThisValue
则用于绑定函数调用。所以我们可以看到,从箭头函数中解析就像解析变量一样:检查当前环境是否存在this
"lexical"
this
this
绑定并且没有找到一个(因为this
调用箭头函数时没有绑定),它进入包含环境。