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

让我们举个例子.我们有一个计数循环,希望在控制台上打印计数器变量.因此,我们使用setTimeoutclosures来捕获计数器变量的值,以确保它不会打印N倍于N的值.

如果没有closures或接近closures,错误的解决方案是:

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

当然,它会在循环后打印10倍于i的值,即10.

So his attempt was:

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

按预期打印0到9.

我告诉他,他不是用closure捕捉i个,但他坚持说他是.通过将for循环体放在另一个setTimeout中(将他的匿名函数传递到setTimeout),再次打印10次,我证明了他没有使用closures.如果我将他的函数存储在var中,并在after循环中执行它,也会打印10次10,这同样适用.所以我的论点是he doesn't really capture the value of 100,使他的版本not结束.

My attempt was:

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

所以我捕获了i(在闭包中命名为i2),但现在我需要另一个函数并传递它.In my case, the function passed to setTimeout really captures 100.

Now who is using closures and who isn't?

请注意,两个解决方案都在控制台上延迟打印0到9,因此它们解决了原始问题,但我们希望了解这两个解决方案uses closures中的哪一个可以完成此任务.

推荐答案

Editor's Note: All functions in JavaScript are closures as explained in this post. However we are only interested in identifying a subset of these functions which are interesting from a theoretical point of view. Henceforth any reference to the word closure will refer to this subset of functions unless otherwise stated.

关于闭包的一个简单解释:

  1. 取一个函数.我们叫它F.
  2. 列出F的所有变量.
  3. 变量可以有两种类型:
    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. 如果F在that父范围之外是referenced,则它成为that自由变量的闭包.
    3. That个自由变量称为闭包F的一个上限值.

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

Case 1: Your Friend's Program

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

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

对于f:

  1. List the variables:
    1. i2是一个local变量.
    2. i是一个free变量.
    3. setTimeout是一个free变量.
    4. g是一个local变量.
    5. console是一个free变量.
  2. Find the parent scope to which each free variable is bound:
    1. i是全局的bound.
    2. setTimeout对于全局范围是bound.
    3. console对于全局范围是bound.
  3. 函数referenced在哪个范围内?global scope号.
    1. 以是i不是closed over乘以f.
    2. 因此setTimeout不是closed over乘以f.
    3. 因此console不是closed over乘以f.

因此,函数f不是闭包.

g美元:

  1. List the variables:
    1. console是一个free变量.
    2. i2是一个free变量.
  2. Find the parent scope to which each free variable is bound:
    1. console对于全局范围是bound.
    2. i2等于bound等于f.
  3. 功能referenced在哪个范围内?scope of 100.

因此,函数g是自由变量i2(它是g的上值)when的闭包,它是setTimeout内的referenced.

Bad for you:您的朋友正在使用闭合.内部函数是一个闭包.

Case 2: Your Program

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

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

对于f:

  1. List the variables:
    1. i2是一个local变量.
    2. g是一个local变量.
    3. console是一个free变量.
  2. Find the parent scope to which each free variable is bound:
    1. console对于全局范围是bound.
  3. In which scope is the function referenced? The global scope.
    1. 因此,console不是closed over乘以f.

因此,函数f不是闭包.

g美元:

  1. List the variables:
    1. console是一个free变量.
    2. i2是一个free变量.
  2. Find the parent scope to which each free variable is bound:
    1. console对于全局范围是bound.
    2. i2等于bound等于f.
  3. 功能referenced在哪个范围内?scope of 100.

因此,函数g是自由变量i2(它是g的上值)when的闭包,它是setTimeout内的referenced.

Good for you:你正在使用闭包.内部功能是一个闭包.

所以你和你的朋友都在使用闭包.别再争论了.我希望我澄清了闭包的概念,以及如何为你们两人识别闭包.

Edit:关于为什么所有函数都是闭包的简单解释(credits@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都不是from the above definition.
  2. 当我们执行程序we expect时,message将被警告because regularFunction不是一个闭包(即,它可以访问all其父范围内的变量,包括message).
  3. 当我们执行程序we observe时,message确实被警告了.

接下来让我们考虑下面的程序(alternative):

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是一个from the above definition.
  2. 当我们执行程序we expect message not be alerted because closureFunction是一个闭包(即,它只能访问the time the function is created(see this answer)处的所有non-local variables,这不包括message).
  3. 当我们执行程序we observe时,message实际上被提醒了.

从这一点我们可以推断出什么呢?

  1. JavaScript解释器处理闭包的方式与处理其他函数的方式没有什么不同.
  2. Every function carries its scope chain along with it. Closures don't have a separate referencing environment.
  3. 闭包和其他函数一样.当它们在范围outside中为referenced时,我们称之为闭包.它们所属的范围because这是一个有趣的例子.

Javascript相关问答推荐

如何通过继承contentitable属性的元素捕捉keydown事件?

如何循环访问对象数组并以关键值形式获得结果?

未捕获错误:在注销后重定向到/login页面时找不到匹配的路由

将自定义排序应用于角形数字数组

实现JS代码更改CSS元素

变量的值在Reaction组件中的Try-Catch语句之外丢失

单个HTML中的多个HTML文件

在数组中查找重叠对象并仅返回那些重叠对象

回溯替代方式

将对象推送到数组会导致复制

无法重定向到Next.js中的动态URL

不同表的条件API端点Reaction-redux

使用RxJS from Event和@ViewChild vs KeyUp事件和RxJS主题更改输入字段值

使用线性插值法旋转直线以查看鼠标会导致 skip

Next.js无法从外部本地主机获取图像

用另一个带有类名的div包装元素

将Singleton实例设置为未定义后的Angular 变量引用持久性行为

未找到用于 Select 器的元素:in( puppeteer 师错误)

Plotly.js栏为x轴栏添加辅助文本

将Windows XP转换为原始数据以在html前端中显示