小编典典

JavaScript 闭包与匿名函数

all

我和我的一个朋友目前正在讨论什么是 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,但他坚持认为他是。我通过将 for 循环体放入另一个循环体(将他的匿名函数传递给)来证明他不使用
闭包 ,再次打印 10 次 10 次。如果我将他的函数存储在 a 中并在循环 之后 执行它,同样适用,也打印 10 乘以 10。所以我的论点是
他并没有真正 捕获 的值 ,使他的版本 不是 闭包。setTimeout``setTimeout``var
__i

我的尝试是:

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

所以我捕获(在闭包中i命名),但现在我 返回 另一个函数并传递它。 就我而言,传递给 setTimeout 的函数确实捕获了
.i2 __ i

现在谁在使用闭包,谁不使用?

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


阅读 87

收藏
2022-03-08

共1个答案

小编典典

编者注:JavaScript 中的
所有
函数都是闭包,如本文所述。然而,我们只对确定这些函数的子集感兴趣,这些函数从理论的角度来看是有趣的。 此后,除非另有说明,否则对闭包
一词的任何引用都将指代此功能子集。

闭包的简单解释:

  1. 取一个函数。我们称它为 F。
  2. 列出 F 的所有变量。
  3. 变量可能有两种类型:
    1. 局部变量(绑定变量)
    2. 非局部变量(自由变量)
  4. 如果 F 没有自由变量,则它不能是闭包。
  5. 如果 F 有任何自由变量(在F 父范围中定义),则:
    1. F 的父作用域必须只有一个 自由 变量绑定到的父作用域。
    2. 如果 F从 该* 父范围之外被 引用 ,则它成为 自由变量的闭包。 ***
    3. 自由变量称为闭包 F 的上值。

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

案例一:你朋友的节目

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

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

对于f

  1. 列出变量:
    1. i2是一个 局部 变量。
    2. i是一个 自由 变量。
    3. setTimeout是一个 自由 变量。
    4. g是一个 局部 变量。
    5. console是一个 自由 变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. i 绑定 到全局范围。
    2. setTimeout 绑定 到全局范围。
    3. console 绑定 到全局范围。
  3. 函数引用 在哪个范围内? 全局 范围 。
    1. 因此i不被 关闭f
    2. 因此setTimeout不被 关闭f
    3. 因此console不被 关闭f

因此该函数f不是闭包。

对于g

  1. 列出变量:
    1. console是一个 自由 变量。
    2. i2是一个 自由 变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. console 绑定 到全局范围。
    2. i2 绑定 在的范围内f
  3. 函数引用 在哪个范围内?的 范围setTimeout
    1. 因此console不被 关闭g
    2. 因此i2关闭g。_

因此, 当* 从内部 引用g该函数时,该函数是自由变量i2(它是 的上值)g的闭包。 ***setTimeout

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

案例 2:您的程序

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

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

对于f

  1. 列出变量:
    1. i2是一个 局部 变量。
    2. g是一个 局部 变量。
    3. console是一个 自由 变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. console 绑定 到全局范围。
  3. 函数引用 在哪个范围内? 全局 范围 。
    1. 因此console不被 关闭f

因此该函数f不是闭包。

对于g

  1. 列出变量:
    1. console是一个 自由 变量。
    2. i2是一个 自由 变量。
  2. 查找每个自由变量绑定到的父作用域:
    1. console 绑定 到全局范围。
    2. i2 绑定 在的范围内f
  3. 函数引用 在哪个范围内?的 范围setTimeout
    1. 因此console不被 关闭g
    2. 因此i2关闭g。_

因此, 当* 从内部 引用g该函数时,该函数是自由变量i2(它是 的上值)g的闭包。 ***setTimeout

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

所以你和你的朋友都在使用闭包。别吵了。我希望我清除了闭包的概念以及如何为你们俩识别它们。

编辑: 关于为什么所有函数都关闭的简单解释(学分@Peter):

首先让我们考虑以下程序(它是控件):

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中,我们知道两者regularFunction都不是闭包。 *
  2. 当我们执行程序时, 我们希望 message收到警报 ,因为 regularFunction它不是闭包(即它可以访问其父范围内的 所有message变量 - 包括)。
  3. 当我们执行程序时, 我们观察message它确实被警告了。

接下来让我们考虑以下程序(它是替代方案):

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. 从上面的定义* 我们知道 onlyclosureFunction是一个闭包。 *
  2. 当我们执行程序时, 我们希望 message不会收到警报 ,因为 它是一个闭包(即它只能在 创建函数时closureFunction访问其所有 非局部变量 。”范围 - JavaScript 闭包与匿名函数 - 代码日志”)message
  3. 当我们执行程序时, 我们观察message它实际上是被警告的。

我们从中推断出什么?

  1. JavaScript 解释器对待闭包的方式与对待其他函数的方式没有区别。
  2. 每个函数都带有它的作用域链。闭包没有 单独 的引用环境。
  3. 闭包就像所有其他函数一样。当它们在它们所属范围 之外的范围内被* 引用 时,我们只称它们为闭包, 因为 这是一个有趣的案例。 ***
2022-03-08