ES6箭头功能是否仍然关闭"这个"即使他们不使用它?

时间:2016-03-05 11:09:12

标签: javascript ecmascript-6 arrow-functions

我试图理解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}在这种情况下收集垃圾?

1 个答案:

答案 0 :(得分:42)

  

我的问题是:箭头函数是否仍然在词法上与此绑定,只要其他人还活着就保持Foo活着,或者在这种情况下Foo是否可以被垃圾收集?

就规范而言,arrow函数引用了创建它的环境对象,该环境对象有thisthis引用{{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()个实例仍在那里:

enter image description here

然后我更改了回调,因此它没有引用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()实例(我放在那里以便在快照中找到它):

enter image description here

我想知道是否回调是完全为空而允许优化,并且惊喜地发现它不是: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是在调用箭头函数时绑定,它进入包含环境。