JavaScript闭包与匿名函数

我的一个朋友和我目前正在讨论JS中的闭包和不闭包。我们只想确保我们正确理解它。

让我们来看这个例子。我们有一个计数循环,希望延迟在控制台上打印计数器变量。因此,我们使用setTimeout闭包捕获计数器变量的值,以确保其不会打印N倍于值N的值。

没有闭包或接近闭包的错误解决方案是:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

当然,它将i在循环后输出10倍的值,即10。

所以他的尝试是:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

按预期打印0到9。

I told him that he isn't using a closure to capture i, but he insists that he is. I proved that he doesn't use closures by putting the for loop body within another setTimeout (passing his anonymous function to setTimeout), printing 10 times 10 again. The same applies if I store his function in a var and execute it after the loop, also printing 10 times 10. So my argument is that he doesn't really capture the value of i, making his version not a closure.

My attempt was:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

So I capture i (named i2 within the closure), but now I return another function and pass this around. In my case, the function passed to setTimeout really captures i.

Now who is using closures and who isn't?

Note that both solutions print 0 to 9 on the console delayed, so they solve the original problem, but we want to understand which of those two solutions uses closures to accomplish this.

Jim西门2020/03/12 14:48:38

让我们看一下两种方式:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

声明并立即执行setTimeout()在其自身上下文中运行的匿名函数的当前值i是通过将副本复制为i2first 来保留的它之所以有效是因为立即执行。

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

声明内部函数的执行上下文,从而将的当前值i保存到i2这种方法还使用立即执行来保留值。

重要

应该提到的是,两种方法之间的运行语义是不同的。您的内部函数将传递给setTimeout()而他的内部函数将setTimeout()自己调用

将两个代码包装在另一个代码中setTimeout()并不能证明只有第二种方法使用了闭包,开始的东西并不相同。

结论

两种方法都使用闭包,所以这取决于个人喜好。第二种方法更容易“移动”或概括。

小哥Tony2020/03/12 14:48:38

您和您的朋友都使用闭包:

闭包是一种特殊的对象,它结合了两件事:一个函数以及创建该函数的环境。环境由创建闭包时在范围内的任何局部变量组成。

MDN:https//developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

在您朋友的代码函数function(){ console.log(i2); }内部定义的闭包匿名函数function(){ var i2 = i; ...,可以读写局部变量i2

在您的代码函数中,在函数function(){ console.log(i2); }闭包内部定义function(i2){ return ...并且可以读/写本地有价值的东西i2(在这种情况下声明为参数)。

在这两种情况下,函数都function(){ console.log(i2); }传递给setTimeout

另一个等效项(但内存利用率较低)是:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}
Green老丝Itachi2020/03/12 14:48:38

仔细检查后,看起来你们俩都在使用闭包。

在您的朋友的情况下,i可在匿名函数1内部i2进行访问,在存在的匿名函数2中进行访问console.log

在您的情况下,您要访问存在i2于其中的匿名函数console.log在chrome开发人员工具的“作用域变量”下和debugger;之前添加一条语句console.log,它将告诉变量该变量在什么作用域下。

SamItachi阿飞2020/03/12 14:48:38

根据closure定义:

“闭包”是一个表达式(通常是一个函数),可以将自由变量绑定这些变量环境(“关闭”表达式)结合在一起

您正在使用closure是否定义了一个函数,该函数使用在函数外部定义的变量。(我们将该变量称为自由变量)。
它们都使用closure(即使在第一个示例中也是如此)。

别坑我路易2020/03/12 14:48:38

你们都在使用闭包。

在这里使用Wikipedia定义

在计算机科学中,闭包(也称为词法闭包或函数闭包)是函数或对函数的引用以及引用环境,即一个表,该表存储对该函数的每个非局部变量(也称为自由变量)的引用。与普通函数指针不同,闭包允许函数访问那些非局部变量,即使在其直接词法范围之外调用该函数也是如此。

您朋友的尝试i通过获取值并将其副本存储到local中,显然使用了非local 变量i2

您自己的尝试i(在调用站点的范围内)作为参数传递给匿名函数。到目前为止,这不是一个闭包,但是该函数返回另一个引用了same的函数i2由于内部匿名函数内部i2不是本地函数,因此将创建一个闭包。