JavaScript 闭包与匿名函数

2024-05-05

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

我们以这个例子为例。我们有一个计数循环,想要在控制台上延迟打印计数器变量。因此我们使用setTimeout and closures捕获计数器变量的值,以确保它不会打印 N 倍的值 N。

错误的解决方案没有closures或任何接近closures将会:

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。

我告诉他他没有使用closure捕捉i,但他坚持认为他是。我证明他不使用closures将 for 循环体放入另一个循环体中setTimeout(将他的匿名函数传递给setTimeout),再次打印 10 次 10。如果我将他的函数存储在一个中,这同样适用var并执行它after循环,也打印 10 次 10。所以我的论点是他真的不capture的价值i,制作他的版本not一个关闭。

我的尝试是:

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

所以我捕捉i (named i2在闭包内),但现在我return另一个函数并传递它。就我而言,传递给 setTimeout 的函数确实捕获了i.

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

请注意,这两个解决方案都会延迟在控制台上打印 0 到 9,因此它们解决了原始问题,但我们想了解这两个解决方案中的哪一个使用闭包来实现这一目标。


编者注:JavaScript 中的所有函数都是闭包,如此处所述post http://javascriptweblog.wordpress.com/2010/10/25/understanding-javascript-closures/。然而,我们只对识别这些函数的子集感兴趣,它们是有趣的 https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures从理论的角度来看。从此以后任何对这个词的引用closure除非另有说明,否则将引用该函数子集。

对闭包的简单解释:

  1. 取一个函数。我们姑且称之为F吧。
  2. 列出F的所有变量。
  3. The variables may be of two types:
    1. 局部变量(绑定变量)
    2. 非局部变量(自由变量)
  4. 如果 F 没有自由变量,那么它不能是闭包。
  5. If F has any free variables (which are defined in a parent scope of F) then:
    1. F 必须只有一个父作用域a自由变量是绑定的。
    2. If F is 引用的从外面that父作用域,那么它就变成了闭包that自由变量。
    3. That自由变量称为闭包 F 的上值。

现在让我们用它来找出谁使用闭包,谁不使用(为了解释起见,我命名了这些函数):

案例 1:您朋友的程序

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

上面的程序中有两个函数:f and g。让我们看看它们是否是闭包:

For f:

  1. List the variables:
    1. i2 is a local多变的。
    2. i is a free多变的。
    3. setTimeout is a free多变的。
    4. g is a local多变的。
    5. console is a free多变的。
  2. Find the parent scope to which each free variable is bound:
    1. i is bound到全球范围。
    2. setTimeout is bound到全球范围。
    3. console is bound到全球范围。
  3. In which scope is the function referenced? The global scope.
    1. Hence i is not 关闭 by f.
    2. Hence setTimeout is not 关闭 by f.
    3. Hence console is not 关闭 by f.

因此函数f不是一个闭包。

For g:

  1. List the variables:
    1. console is a free多变的。
    2. i2 is a free多变的。
  2. Find the parent scope to which each free variable is bound:
    1. console is bound到全球范围。
    2. i2 is bound到范围f.
  3. In which scope is the function referenced? The scope of setTimeout.
    1. Hence console is not 关闭 by g.
    2. Hence i2 is 关闭 by g.

因此函数g是自由变量的闭包i2(这是一个上值g) when it's 引用的从内部setTimeout.

对你不好:你的朋友正在使用闭包。内部函数是一个闭包。

案例 2:您的程序

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

上面的程序中有两个函数:f and g。让我们看看它们是否是闭包:

For f:

  1. List the variables:
    1. i2 is a local多变的。
    2. g is a local多变的。
    3. console is a free多变的。
  2. Find the parent scope to which each free variable is bound:
    1. console is bound到全球范围。
  3. In which scope is the function referenced? The global scope.
    1. Hence console is not 关闭 by f.

因此函数f不是一个闭包。

For g:

  1. List the variables:
    1. console is a free多变的。
    2. i2 is a free多变的。
  2. Find the parent scope to which each free variable is bound:
    1. console is bound到全球范围。
    2. i2 is bound到范围f.
  3. In which scope is the function referenced? The scope of setTimeout.
    1. Hence console is not 关闭 by g.
    2. Hence i2 is 关闭 by g.

因此函数g是自由变量的闭包i2(这是一个上值g) when it's 引用的从内部setTimeout.

对你有好处:您正在使用闭包。内部函数是一个闭包。

所以你和你的朋友都在使用闭包。别再争论了。我希望我已经清楚了闭包的概念以及如何为你们俩识别它们。

Edit:关于为什么所有函数都是闭包的简单解释(鸣谢@Peter):

首先让我们考虑以下程序(它是control http://jsfiddle.net/KyQKw/):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}
  1. 我们知道,两者lexicalScope and regularFunction不是关闭从上面的定义.
  2. 当我们执行程序时我们期待 message被提醒because regularFunction不是一个闭包(即它可以访问all其父作用域中的变量 - 包括message).
  3. 当我们执行程序时我们观察到 that message确实是警觉了。

接下来让我们考虑以下程序(它是选择 http://jsfiddle.net/KyQKw/1/):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}
  1. 我们知道,只有closureFunction是一个闭包从上面的定义.
  2. 当我们执行程序时我们期待 message不被警告because closureFunction是一个闭包(即它只能访问它的所有非局部变量 at 函数创建的时间 (看到这个答案 https://stackoverflow.com/a/12930799/783743) - 这不包括message).
  3. 当我们执行程序时我们观察到 that message实际上正在被警告。

我们由此推断出什么?

  1. JavaScript 解释器对待闭包的方式与对待其他函数的方式没有什么不同。
  2. 每个函数都有它的作用域链 http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/与它一起。闭包没有separate参考环境。
  3. 闭包就像其他所有函数一样。当它们发生时我们就称它们为闭包引用的在一定范围内outside他们所属的范围because这是一个有趣的案例。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript 闭包与匿名函数 的相关文章

随机推荐