我的一个朋友和我正在讨论什么是JS中的闭包,什么不是。我们只是想确保我们理解正确。

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

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

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

这当然会在循环后输出10倍I的值,也就是10。

所以他的尝试是:

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

按预期打印0到9。

我告诉他,他没有使用闭包来捕获I,但他坚持说他是。我通过将for循环体放在另一个setTimeout(将他的匿名函数传递给setTimeout)中,再次打印10乘以10来证明他没有使用闭包。如果我将他的函数存储在var中,并在循环之后执行它,同样打印10乘以10。所以我的论点是,他没有真正捕获i的值,使得他的版本不是一个闭包。

我的尝试是:

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

所以我捕获了I(在闭包中命名为i2),但现在我返回另一个函数并传递它。在我的例子中,传递给setTimeout的函数实际上捕获了i。

现在谁在使用闭包,谁没有呢?

请注意,这两个解决方案都延迟在控制台上打印0到9,因此它们解决了最初的问题,但我们想了解这两个解决方案中哪一个使用闭包来完成这一任务。


当前回答

根据闭包定义:

“闭包”是一个表达式(通常是一个函数),它可以有自由变量和绑定这些变量的环境(“关闭”表达式)。

如果你定义的函数使用了一个在函数外部定义的变量,那么你就是在使用闭包。(我们称这个变量为自由变量)。 它们都使用闭包(甚至在第一个例子中)。

其他回答

你们都在使用闭包。

我在这里引用维基百科的定义:

在计算机科学中,闭包(也称为词法闭包或函数) 闭包)是一个函数或对函数的引用 引用环境—存储对每个类的引用的表 该函数的非局部变量(也称为自由变量)。 闭包(与普通函数指针不同)允许函数进行访问 这些非局部变量即使在其immediate之外调用 词法作用域。

您的朋友的尝试显然使用了变量i,这是非本地的,通过获取其值并复制到本地i2中。

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

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

闭包是一种特殊的对象,它结合了两个东西:函数和创建该函数的环境。环境由创建闭包时范围内的任何局部变量组成。 MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

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

在代码中,函数function(){console.log(i2);}定义在函数闭包内的函数函数(i2){返回…并且可以读/写本地值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);
}

根据闭包定义:

“闭包”是一个表达式(通常是一个函数),它可以有自由变量和绑定这些变量的环境(“关闭”表达式)。

如果你定义的函数使用了一个在函数外部定义的变量,那么你就是在使用闭包。(我们称这个变量为自由变量)。 它们都使用闭包(甚至在第一个例子中)。

让我们看看这两种方式:

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

声明并立即执行一个匿名函数,该函数在自己的上下文中运行setTimeout()。i的当前值通过先复制到i2来保存;它之所以有效,是因为可以立即执行。

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

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

重要的

应该提到的是,两种方法之间的运行语义是不一样的;你的内部函数被传递给setTimeout(),而他的内部函数调用setTimeout()本身。

将这两种代码包装在另一个setTimeout()中并不能证明只有第二种方法使用闭包,只是一开始就不一样。

结论

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

我之前写这篇文章是为了提醒自己闭包是什么以及它在JS中是如何工作的。

闭包是一种函数,它在被调用时使用声明它的作用域,而不是调用它的作用域。在javaScript中,所有函数都是这样的。只要函数仍然指向作用域中的变量值,作用域中的变量值就会持续存在。该规则的例外是'this',它指的是函数被调用时所在的对象。

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2'