一尘不染

JavaScript闭包与匿名函数

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,但他坚持认为自己是。我通过将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);
}

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

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

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


阅读 301

收藏
2020-04-25

共1个答案

一尘不染

编者按:
在JavaScript中所有的功能都关闭在这个解释后但是,我们只对确定这些功能的子集感兴趣,这从理论上讲是很[有趣的。此后,除非另有说明,否则对闭包一词的任何引用都将指代此功能子集。

闭包的简单说明:

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

现在,让我们用它来找出谁使用闭包,谁不使用闭包(为便于说明,我将函数命名为):

情况1:您朋友的程序

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(这是用于的upvalue 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(这是用于的upvalue g 它的 引用 从内setTimeout

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

因此,您和您的朋友都在使用闭包。别吵了 我希望我清除了闭包的概念以及如何为你们两个人识别它们。

编辑: 关于为什么所有函数都关闭的简单说明(点数@Peter):

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

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. 我们知道,无论lexicalScoperegularFunction不封闭 ,从上面的定义
  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. 我们知道,这只是 上述定义*closureFunction的闭包。 *
  2. 当我们执行程序时, 我们希望* message不会 因为 closureFunction闭包而收到警告(即,在 创建函数时, 它只能访问其所有 非局部变量请参见此答案)-不包括)。 *message
  3. 当我们执行程序时, 我们观察message实际上正在被警告。

我们从中得出什么呢?

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