ES6箭头功能仍然靠近“这个”即使他们不使用它? [英] Do ES6 arrow functions still close over "this" even if they don't use it?

查看:142
本文介绍了ES6箭头功能仍然靠近“这个”即使他们不使用它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力了解 在ES6箭头函数中是否具有约束条件的规则。我们先看看这个:

  function Foo(other){
other.callback =()=> {this.bar(); };

this.bar = function(){
console.log('bar called');
};
}

当我构建一个新的Foo(其他) / code>,在另一个对象上设置一个回调。回调是一个箭头函数,箭头函数中的这个被词法绑定到 Foo 实例,所以 Foo 不会垃圾回收,即使我没有保留任何其他引用的 Foo

  function Foo(other) {
other.callback =()=> {};
}

现在我将回调设置为nop,我从来没有提到这个里面。 我的问题是:箭头功能仍然按照这个绑定,保持 Foo 只要其他还活着,或者在这种情况下可能会垃圾收集 Foo

解决方案


我的问题是:箭头功能仍然在词法上绑定到这个,只要其他的还​​活着,或者在这种情况下可能是垃圾收集?


就规格而言,箭头函数引用了创建它的环境对象,该环境对象具有这个,而这个指由该调用创建的 Foo 实例。所以任何依赖于 Foo 而不是保存在内存中的代码依赖于优化,而不是指定的行为。



Re优化,归结为您使用的JavaScript引擎是否优化了关闭,以及是否可以在特定情况下优化关闭。 (一些事情可以阻止它)。这种情况就像这样正常的功能:

  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 = - exposed-gc)并运行:



 use strict; function Foo(other){other.callback =()=>这个; //< ==注意使用`this`作为返回值} let a = []; for(let n = 0; n <10000; ++ n){a [n] = {};新的Foo(a [n]);} //让我们保持一个Foo只是为了方便在堆中找到snapshotlet 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);}  



使用堆快照显示内存中预期的10,001个 Foo 实例。然后我在控制台中执行了 gc()(这就是强制垃圾回收的方式),并采取了另一个堆快照。 10,001 Foo 实例仍然存在:



然后我更改了回调,所以它没有引用这个

  other.callback =()=> {}; //< ==不再```

 use strict; function Foo(other){other.callback =()=> {}; //< == no more`this`} let a = []; for(let n = 0; n <10000; ++ n){a [n] = {};新的Foo(a [n]);} //让我们保持一个Foo只是为了方便在堆中找到snapshotlet 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(),内存中只有一个 Foo 实例在代码完成运行时,放在那里可以轻松找到快照):





我想知道回调是否完全是空的,允许优化,并惊喜地发现它不是:Chrome很开心保留关闭的部分,同时放弃这个,如下所示:



 use strict; function Foo(other,x){other.callback =()=> x * 2;} let a = []; for(let n = 0; n <10000; ++ n){a [n] = {};新的Foo(a [n],n);} //让我们保持一个Foo只是为了容易找到堆snapshotlet 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(完成,单击按钮使用回调);函数日志){let p = document.createElement('p'); p.appendChild(document.createTextNode(MSG)); document.body.appendChild(p);}  

 输入type =buttonid =btn-callvalue =调用随机回调>  



尽管回调是在那里,并且引用 x ,Chrome可以优化 Foo
$ b

你询问了关于这个在箭头函数中解决:机制遍布整个规范。每个环境(如因为通过调用函数创建的环境)具有一个 [[thisBindingStatus]] 内部插槽,它是lexical用于箭头功能。当确定 的值时,内部操作 ResolveThisBinding 被使用,它使用内部 GetThisEnviroment 操作来查找具有这个定义。当进行正常功能调用时, BindThisValue 用于绑定用于函数调用,如果环境不是lexical环境。所以我们可以看到,从箭头函数中解析这个就像解析一个变量:当前环境被检查一个这个绑定,而不是找到一个(因为在调用箭头函数时没有这个被绑定),它会进入包含环境。


I'm trying to understand the rules of when this is lexically bound in an ES6 arrow function. Let's first look at this:

function Foo(other) {
    other.callback = () => { this.bar(); };

    this.bar = function() {
        console.log('bar called');
    };
}

When I construct a new Foo(other), a callback is set on that other object. The callback is an arrow function, and the this in the arrow function is lexically bound to the Foo instance, so the Foo won't be garbage collected even if I don't keep any other reference to the Foo around.

What happens if I do this instead?

function Foo(other) {
    other.callback = () => { };
}

Now I set the callback to a nop, and I never mention this in it. My question is: does the arrow function still lexically bind to this, keeping the Foo alive as long as other is alive, or may the Foo be garbage collected in this situation?

解决方案

My question is: does the arrow function still lexically bind to this, keeping the Foo alive as long as other is alive, or may the Foo be garbage collected in this situation?

As far as the specification is concerned, the arrow function has a reference to the environment object where it was created, and that environment object has this, and that this refers to the Foo instance created by that call. So any code relying on that Foo not being kept in memory is relying on optimization, not specified behavior.

Re optimization, it comes down to whether the JavaScript engine you're using optimizes closures, and whether it can optimize the closure in the specific situation. (A number of things can prevent it.) The situation just like this with "normal" functions:

function Foo(other) {
    var t = this;
    other.callback = function() { };
}

In that situation, the function closes over the context containing t, and so in theory, has a reference to t which in turn keeps the Foo instance in memory.

That's the theory, but in practice a modern JavaScript engine can see that t is not used by the closure and can optimize it away provided doing so doesn't introduce an observable side-effect. Whether it does and, if so, when, is entirely down to the engine.

Since arrow functions truly are lexical closures, the situations are exactly analogous and so you'd expect the JavaScript engine to do the same thing: Optimize it away unless it causes a side-effect that can be observed. That said, remember that arrow functions are very new, so it may well be that engines don't have much optimization around this yet (no pun).

The version of V8 in Chrome (I'm using Chrome 48.0.2564.116 64-bit) does currently seem to do this: I ran Chrome in the mode that allows you to force garbage collection (google-chrome --js-flags="--expose-gc") and ran this:

"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);
}

In Dev Tools, taking a heap snapshot shows the expected 10,001 instances of Foo in memory. Then I executed gc() in the console (this is how you force garbage collection) and took another heap snapshot. The 10,001 Foo instances were still there:

Then I changed the callback so it didn't reference 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);
}

And ran the page again. I didn't even have to gc(), there was just the one Foo instance in memory (the one I put there to make it easy to find in the snapshot) when the code finished running:

I wondered if it were the fact that the callback is completely empty that allowed the optimization, and was pleasantly surprised to find that it wasn't: Chrome is happy to retain parts of the closure while letting go of this, as demonstrated here:

"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">

Despite the fact that the callbacks are there and have their reference to x, Chrome optimizes the Foo instance away.


You asked about spec references for how this is resolved in arrow functions: The mechanism is spread throughout the spec. Each environment (such as the environment created by calling a function) has a [[thisBindingStatus]] internal slot, which is "lexical" for arrow functions. When determining the value of this, the internal operation ResolveThisBinding is used, which uses the internal GetThisEnviroment operation to find the environment that has this defined. When a "normal" function call is made, BindThisValue is used to bind the this for the function call if the environment is not a "lexical" environment. So we can see that resolving this from within an arrow function is just like resolving a variable: The current environment is checked for a this binding and, not finding one (because no this is bound when calling an arrow function), it goes to the containing environment.

这篇关于ES6箭头功能仍然靠近“这个”即使他们不使用它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆