我试图理解this
在ES6箭头函数中词汇绑定的规则。我们先来看看这个:
function Foo(other) {
other.callback = () => { this.bar(); };
this.bar = function() {
console.log('bar called');
};
}
当我构造new Foo(other)
时,会在另一个对象上设置回调。回调是一个箭头函数,箭头函数中的this
在词法上绑定到Foo
实例,因此Foo
不会被垃圾收集,即使我没有#&# 39;保持对Foo
周围的任何其他引用。
如果我这样做会怎么样?
function Foo(other) {
other.callback = () => { };
}
现在我将回调设置为nop,我从不提及this
。 我的问题是:箭头功能是否仍然词法绑定到this
,只要Foo
还活着就保持other
有效,或者{{1}在这种情况下收集垃圾?
答案 0 :(得分:42)
我的问题是:箭头函数是否仍然在词法上与此绑定,只要其他人还活着就保持Foo活着,或者在这种情况下Foo是否可以被垃圾收集?
就规范而言,arrow函数引用了创建它的环境对象,该环境对象有this
,this
引用{{1}该调用创建的实例。因此,任何依赖于Foo
未保留在内存中的代码依赖于优化,而不是指定的行为。
重新优化,可归结为您使用的JavaScript引擎是否优化了闭包,以及它是否可以在特定情况下优化闭包。 (有很多事情可以阻止它。)这种情况与“普通”功能一样:
Foo
在这种情况下,函数会关闭包含function Foo(other) {
var t = this;
other.callback = function() { };
}
的上下文,因此理论上,它会引用t
,而t
会将Foo
实例保留在内存中。
这就是理论,但在实践中,现代JavaScript引擎可以看到闭包没有使用t
并且可以优化它,前提是这样做不会引入可观察的副作用。它是否确实如此,何时,完全取决于发动机。
由于箭头函数确实是词法闭包,因此情况完全类似,所以你希望JavaScript引擎做同样的事情:优化它除非它引起可以观察到的副作用。也就是说,请记住箭头函数是非常新,因此很可能引擎在此处没有太多优化(没有双关语)。
Chrome中的V8版本(我正在使用Chrome 48.0.2564.116 64位)确实目前似乎是这样做的:我在允许您强制进行垃圾回收的模式下运行Chrome( google-chrome --js-flags="--expose-gc"
)然后运行:
"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);
}
在Dev Tools中,获取堆快照会在内存中显示预期的{00}个{00}个实例。然后我在控制台中执行了Foo
(这就是你强制垃圾收集的方法)并拍摄了另一个堆快照。 10,001 gc()
个实例仍在那里:
然后我更改了回调,因此它没有引用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);
}
,在代码运行完成后,内存中只有一个gc()
实例(我放在那里以便在快照中找到它):
我想知道是否回调是完全为空而允许优化,并且惊喜地发现它不是:Chrome很高兴保留部分关闭放弃Foo
,如下所示:
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">
,但Chrome会优化x
实例。
您询问了有关如何在箭头函数中解析Foo
的规范参考:该机制遍布整个规范。每个environment(例如通过调用函数创建的环境)都有一个this
内部插槽,箭头函数为[[thisBindingStatus]]
。确定"lexical"
的值时,将使用内部操作ResolveThisBinding
,该操作使用内部GetThisEnviroment
操作来查找已定义this
的环境。当进行“正常”函数调用时,如果环境不是this
环境,则BindThisValue
用于绑定函数调用的this
。因此,我们可以看到从箭头函数中解析"lexical"
就像解析变量一样:检查当前环境是否有this
绑定,而不是找到一个(因为没有this
是在调用箭头函数时绑定,它进入包含环境。