Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之闭包 #9

Open
mqyqingfeng opened this issue Apr 27, 2017 · 111 comments
Open

JavaScript深入之闭包 #9

mqyqingfeng opened this issue Apr 27, 2017 · 111 comments

Comments

@mqyqingfeng
Copy link
Owner

@mqyqingfeng mqyqingfeng commented Apr 27, 2017

定义

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 + 函数能够访问的自由变量

举个例子:

var a = 1;

function foo() {
    console.log(a);
}

foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。

那么,函数 foo + foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……

还真是这样的!

所以在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

咦,这怎么跟我们平时看到的讲到的闭包不一样呢!?

别着急,这是理论上的闭包,其实还有一个实践角度上的闭包,让我们看看汤姆大叔翻译的关于闭包的文章中的定义:

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

接下来就来讲讲实践上的闭包。

分析

让我们先写个例子,例子依然是来自《JavaScript权威指南》,稍微做点改动:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();

首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。

另一个与这段代码相似的例子,在《JavaScript深入之执行上下文》中有着非常详细的分析。如果看不懂以下的执行过程,建议先阅读这篇文章。

这里直接给出简要的执行过程:

  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

了解到这个过程,我们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

以上的代码,要是转换成 PHP,就会报错,因为在 PHP 中,f 函数只能读取到自己作用域和全局作用域里的值,所以读不到 checkscope 下的 scope 值。(这段我问的PHP同事……)

然而 JavaScript 却是可以的!

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

所以,让我们再看一遍实践角度上闭包的定义:

  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  2. 在代码中引用了自由变量

在这里再补充一个《JavaScript权威指南》英文原版对闭包的定义:

This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.

闭包在计算机科学中也只是一个普通的概念,大家不要去想得太复杂。

必刷题

接下来,看这道刷题必刷,面试必考的闭包题:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = {
    AO: {
        arguments: {
            0: 0,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。

data[1] 和 data[2] 是一样的道理。

下一篇文章

JavaScript深入之参数按值传递

相关链接

如果想了解执行上下文的具体变化,不妨循序渐进,阅读这六篇:

《JavaScript深入之词法作用域和动态作用域》

《JavaScript深入之执行上下文栈》

《JavaScript深入之变量对象》

《JavaScript深入之作用域链》

《JavaScript深入之从ECMAScript规范解读this》

《JavaScript深入之执行上下文》

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@jawil
Copy link

@jawil jawil commented Apr 27, 2017

支持下一啊,虽然对闭包已经看了很多了,每次看一遍都会有一番不同的感受,学习就是一个重复的过程。

@rivens-77
Copy link

@rivens-77 rivens-77 commented May 10, 2017

请问下学长为什么globalContext = {
VO: {
data: [...],
i: 3
}
}
这里是3??怎么来的 为什么不是0,1,2

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 10, 2017

当执行到data[0]函数的时候,for循环已经执行完了,i是全局变量,此时的值为3,举个例子:

for (var i = 0; i < 3; i++) {}
console.log(i) // 3
@spicychocolate
Copy link

@spicychocolate spicychocolate commented May 12, 2017

循环结束后

data[0] = function(){console.log(i)}
data[1] = function(){console.log(i)}
data[2] = function(){console.log(i)}

执行data[0]()data[1]()data[2]()时,i=3,所以都打印3
这个例子看完N遍后终于知道原理了

@fi3ework
Copy link

@fi3ework fi3ework commented May 22, 2017

匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

里面的arguments0:1为什么是1呢,按我的理解应该和i的值相同,所以不是0: 0

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 22, 2017

@fi3ework 嗯,这里是笔误,感谢指出,o( ̄▽ ̄)d

@xdwxls
Copy link

@xdwxls xdwxls commented Jun 1, 2017

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() { 
        console.log(c); 
        console.log(a);
    }
    fn = innnerFoo; 
}

function bar() {
    var c = 100;
    fn(); 
}

foo();
bar();

大神,帮我把这个例子分析下?自己解释感觉说服不了自己,c 为什么会报错,我怎么感觉会读取到bar 执行上下文中变量对象c

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 1, 2017

@xdwxls 词法作用域的问题,具体可以看第二篇《JavaScript深入之词法作用域和动态作用域》,关于这道题,你可以简单理解为函数能够读取到的值跟函数定义的位置有关,跟执行的位置无关

@tangshuimei
Copy link

@tangshuimei tangshuimei commented Jun 2, 2017

大神,那是不是执行上下文中的作用域scope仅是父级的一个VO记录,不会像跟ECStack那样跟函数执行的次序有关呢?

@tangshuimei
Copy link

@tangshuimei tangshuimei commented Jun 2, 2017

@xdwxls 我觉得可能是虽然fn(),即innerFoo()是在bar里面执行的,但是innerFoo函数的时候他的作用域scope里面分别是[AO,fooContext.AO,globalContext.AO],并没有包括barContext.AO在里面,所以根本就没有声明c这个变量,所以会显示is not define,我就猜猜而已......

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 2, 2017

@tangshuimei 是的,你可以这样理解,如果要更严谨的话,可以说,执行上下文中的作用域 scope 是由函数的 [[scope]]属性初始化,而函数的[[scope]] 属性保存了函数创建时词法层面上的父级们的 VO 引用,跟函数的执行顺序无关。

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 2, 2017

@tangshuimei 哈哈,关于这道题的分析,我赞同你的观点~

@xdwxls
Copy link

@xdwxls xdwxls commented Jun 4, 2017

@tangshuimei 你说 innerFoo函数的时候他的作用域scope里面分别是[AO,fooContext.AO,globalContext.AO], 那这个时候AO 具体表示什么呢???有点费解

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 4, 2017

@xdwxls AO 表示活动对象,储存了函数的参数、函数内声明的变量等,在 innnerFoo 中,查找变量 c,就要在 innerFoo 函数的作用域链,也就是 [AO,fooContext.AO,globalContext.AO] 中找到变量 c 的声明,因为没有,所以最终会报错~

@frankchou1
Copy link

@frankchou1 frankchou1 commented Jul 18, 2017

我在想,我们在想这个作用域链的时候是不是把for循环的AO给漏了?比如说下面这个例子:

var data = [ ];
for( var i=0; i<3 ; i++ ){
        data [ i ] = function ( ) {
            console.log ( i );
        };
       data [ i ]( i );
}

这里返回的是1,2,3

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jul 19, 2017

@frankchou1 for 循环不会创建一个执行上下文,所有不会有 AO, i 的值是在全局对象的 AO 中,代码初始的时候为:

globalContext = {
    VO: {
        data: [...],
        i: 0
    }
}

代码执行的时候,不断修改 i 的值

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jul 19, 2017

@frankchou1 看你修改了几次格式,Github 的评论支持 markdown 格式,使用代码块可以用 ```js 和 ``` 包裹

@Muscliy
Copy link

@Muscliy Muscliy commented Jul 20, 2017

var data = [];

for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}

data0;
data1;
data2;
求教下,要是把var i 改成let 这个原理有事怎么样子的呢?

@bighuang624
Copy link

@bighuang624 bighuang624 commented Jul 20, 2017

@Muscliy let 关键字将 for 循环的块隐式地声明为块作用域。而 for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
这是《你不知道的 JavaScript》中的解释。

@Muscliy
Copy link

@Muscliy Muscliy commented Jul 20, 2017

@bighuang624 谢谢,我刚刚用babel 转了下发现其实多了_loop的函数,这个就解释的通了,看来《你不知道的 JavaScript》 这个书很好
"use strict";

var data = [];

var _loop = function _loop(i) {
data[i] = function () {
console.log(i);
};
};

for (var i = 0; i < 3; i++) {
_loop(i);
}

data0;
data1;
data2;

@keyiran
Copy link

@keyiran keyiran commented Aug 20, 2017

非常感谢博主!以前对闭包总是雾里看花,终隔一层,提到闭包,有人说函数就是闭包,有人说必须是嵌套,又是引用怎么怎么样,其实现在看来,两者都是,只不过是一种狭义和广义上概念的区别。
另外,通过楼主的分析,渐渐发现,只要理清了变量的查找规则,AO对象词法分析期和执行期的变化,闭包这东西,正是基于这些规则下产生的一种自然而然的现象。

@alicejxr
Copy link

@alicejxr alicejxr commented Sep 19, 2017

看了这么多写闭包的,这个是我看完之后唯一恍然大悟的,之前都是一知半解的。感谢,比心💟

@dengnan123
Copy link

@dengnan123 dengnan123 commented Oct 30, 2017

学习了啊

@jasonzhangdong
Copy link

@jasonzhangdong jasonzhangdong commented Nov 19, 2017

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

data[0]的时候i不是0吗?为什么是3,整个循环走完了吗?

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Nov 20, 2017

@jasonzhangdong 正是如此,data[0] 是一个函数名,data0 表示执行这个函数,当执行函数的时候,循环已经走完了,i 的值为 3:

for (var i = 0; i < 3; i++) {

}
console.log(i) // 3
@yzStrive
Copy link

@yzStrive yzStrive commented May 19, 2020

个人理解:所谓的必包是一个函数,创建这个函数的作用域已经销毁,但是这个函数还存在,而且引用了它上级作用域的变量,类似上级作用域的私有变量的getter或者setter

@xieyezi
Copy link

@xieyezi xieyezi commented Jun 15, 2020

结合前面的文章反反复复看了很多遍,终于懂了,大爱作者😘

@xsfxtsxxr
Copy link

@xsfxtsxxr xsfxtsxxr commented Jun 30, 2020

例子

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

赋值

因为data[i] = 一个匿名自执行函数。
所以在给data[i]赋值之前就已经执行了函数
因此:

data[0] = function(){console.log(0)}
data[1] = function(){console.log(1)}
data[2] = function(){console.log(2)}

执行

data[0](); // 0
data[1](); // 1
data[2](); // 2

不知道这样理解的对不对

@g1f9
Copy link

@g1f9 g1f9 commented Jul 4, 2020

es2015 的规范使用 Lexical Environment 来解释,大佬可以更新一波

@zhaoyan11
Copy link

@zhaoyan11 zhaoyan11 commented Jul 5, 2020

“从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量”

老兄,感觉这两句话可以再推敲一下呢?
第一句,“即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)”
你的意思我应该明白了,但是给人一种创建它的上下文销毁可能销毁、可能没销毁的感觉。

第二句,在代码中引用了自由变量,那如果下面两种算不算闭包呢?
情况1:
var x = 1;
function f() {
return function () { console.log(x) }
}
var theFn = f()

情况2:
function f() {
var x = 1;
return function () {
return function() { console.log(x) }
}
}
var theFn = f()()

感觉情况1不属于我们通常理解的闭包,情况2又属于。

所以,能不能改成:

从实践角度:闭包是:
能够访问已经被销毁的执行期上下文中的活动对象的函数。

@xueyida
Copy link

@xueyida xueyida commented Jul 16, 2020

感觉子自执行函数理解的话比较抽象,拆开的话,博主的意思更容易理解-_-!

// 自执行函数
for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}``

// 等于上面的自执行函数
for(var i = 0; i<3; i++){
    function test(j){
        return function(){
            console.log(j);
        }
    }

    data[i] = test(i);
}
@JackDan9
Copy link

@JackDan9 JackDan9 commented Aug 26, 2020

Hello, 这样的理解不知道对不对啊?
例子和闭包没有任何关系

var data = [];

for(var i = 0; i < 5; i++) {
    data[i] = function() {
        console.log(i);
    }
}

data[0]();
data[1]();
data[2]();
data[3]();
data[4]();


// VS

var data = [];

for(var i = 0; i < 5; i++) {
    data[i] = (function(i) {
        return function() {
            console.log(i)
        }
    })(i)
}

data[0]();
data[1]();
data[2]();
data[3]();
data[4]();

只是通过LIFE人为制造一个函数作用域来弥补es5没有块作用域的缺陷而已。

Hello, 楼主!
不好意思!我新提了一个issue#182,也是关于这个问题点的,请楼主帮忙关闭下吧。

@benyiforever
Copy link

@benyiforever benyiforever commented Aug 27, 2020

@mqyqingfeng 楼主上午好,请教一个问题:下面这些没看懂。。。。请问应该先执行哪个?
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
JavaScript深入之执行上下文,checkscope和f函数的执行顺序如下:
5.执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈
ECStack = [
fContext,
checkscopeContext,
globalContext
];
8.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
ECStack = [
checkscopeContext,
globalContext
];
9.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
而在JavaScript深入之闭包文章中执行顺序颠倒了:
7. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
8. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
9. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
10. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
11. f 执行上下文初始化,创建变量对象、作用域链、this等
12. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

@MrLeihe
Copy link

@MrLeihe MrLeihe commented Sep 21, 2020

看了那么多闭包,终于才知道原理,:)

@MrLeihe
Copy link

@MrLeihe MrLeihe commented Sep 21, 2020

结合作用域链来理解闭包真的和以前对闭包的理解发生了质的改变!

起飞

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

首先感谢你的分享,但是呢看了你写的 js 深入系列这些文章,我是越看越懵比,就拿你这篇文章中例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();

你的解释我是一脸懵比,我直接翻开 javascript权威指南这本书的 183页面,解释非常清楚, 还有我看 阮老师的js标准教程 也是行云流水很流畅,看了你这个系列的 “JavaScript深入之从ECMAScript规范解读this” 也是很懵比,很有读者看了也有跟我一样的感觉,我直接看阮老师的 http://javascript.ruanyifeng.com/oop/this.html 思想很清晰啊,我不知道是不是作者表达的太乱了

从第一篇看到现在,觉得作者讲的非常好,也很透彻

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

“自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量”。下面这个闭包的j参数,应该不是自由变量(是函数参数),但是闭包是可以访问j的。能解答一下吗?

var funArr = []
for (var i = 0; i < 6; i++) {
    funArr[i] = (function(j) {
        return function() {
            console.log(j);
        }
    })(i)
}

这个 j 是最内层函数 `return function() {

        console.log(j);
    }`  中return 出来的匿名函数的自由变量
@anjina
Copy link

@anjina anjina commented Oct 31, 2020

let f;
let o = 10;
function a(o) {
if (!f) {
f = () => {
console.log('console f', o);
}
} else {
console.log('f true', o);
}
o+= 1;
f();
}
a(1); // 2
a(5); // 5,2
这个最后a(5)输出的是还是2却不是6,从原理方面应该怎么解释

当第一次执行a时, f被赋值为箭头函数,此时函数创建 , 会复制所有父对象的变量对象到[[scope]]属性,即[ a.AO, global.VO],然后打印2, 当再次执行 函数a,时形参为5, 会重新创建上下文,并且初始化,但后面f执行为什么打印不是6,就是因为 f [[scope]] 属性引用的上下文是第一次执行函数a时候的上下文, 里面的 o为2 ,不是第二次执行时候的

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

1、即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)2
2、在代码中引用了自由变量
按照上面这个说法 下面的函数就不属于闭包了啊

function a() {
   var c = 1;
  function b(){
    console.log(c)
  }
  b()
}
a()

从理论角度看是, 从实践角度看不是

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

问一下大佬,如果没有引用自由变量,这个自由变量在内存中还存在吗?只是我们访问不到?

这个自由变量 只是相对于当前函数作用域来定义的, 它可能是外层函数定义的变量, 也可能是全局变量

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

“从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量”

老兄,感觉这两句话可以再推敲一下呢?
第一句,“即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)”
你的意思我应该明白了,但是给人一种创建它的上下文销毁可能销毁、可能没销毁的感觉。

第二句,在代码中引用了自由变量,那如果下面两种算不算闭包呢?
情况1:
var x = 1;
function f() {
return function () { console.log(x) }
}
var theFn = f()

情况2:
function f() {
var x = 1;
return function () {
return function() { console.log(x) }
}
}
var theFn = f()()

感觉情况1不属于我们通常理解的闭包,情况2又属于。

所以,能不能改成:

从实践角度:闭包是:
能够访问已经被销毁的执行期上下文中的活动对象的函数。

个人觉得第一种也是吧,函数f返回了一个匿名函数,然后f销毁, 满足条件1, 另外 匿名函数中 引用了自由变量x, 满足条件2。 注意 自由变量是指 不属于当前函数 的参数也不属于当前函数局部变量的参数, 所以x 也是自由变量, 并非只有 引用了 上层函数的参数 才能叫自由变量

@anjina
Copy link

@anjina anjina commented Oct 31, 2020

Hello, 这样的理解不知道对不对啊?
例子和闭包没有任何关系

var data = [];

for(var i = 0; i < 5; i++) {
    data[i] = function() {
        console.log(i);
    }
}

data[0]();
data[1]();
data[2]();
data[3]();
data[4]();


// VS

var data = [];

for(var i = 0; i < 5; i++) {
    data[i] = (function(i) {
        return function() {
            console.log(i)
        }
    })(i)
}

data[0]();
data[1]();
data[2]();
data[3]();
data[4]();

只是通过LIFE人为制造一个函数作用域来弥补es5没有块作用域的缺陷而已。

Hello, 楼主!
不好意思!我新提了一个issue#182,也是关于这个问题点的,请楼主帮忙关闭下吧。

IIFE 的例子也是闭包呀,return 的匿名函数 中 的 i 是自由变量, 而外层返回这个匿名函数的函数销毁了, 匿名函数能通过[[scope]] 属性访问到 外层函数 的AO, 从而 访问 自由变量i

@cw84973570
Copy link

@cw84973570 cw84973570 commented Dec 7, 2020

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

所以让我们改成闭包看看:

这里为什么说改成闭包看看呢?修改之前的函数不是保存了自由变量i,不也是闭包吗?

@xsfxtsxxr
Copy link

@xsfxtsxxr xsfxtsxxr commented Dec 11, 2020

@cw84973570

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

这样写并不是闭包啊,变量 i 没有单独保存,还是用的全局的变量

@cw84973570
Copy link

@cw84973570 cw84973570 commented Dec 11, 2020

@xsfxtsxxr

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

这样写并不是闭包啊,变量 i 没有单独保存,还是用的全局的变量

文章第一个例子中的a就是全局变量啊,按我的理解自由变量是可以包括全局变量的,举个例子:

var a = 1;
function foo(b) {
    var c = 3
    console.log(a);
    console.log(b);
   console.log(c);
}
foo(2)

这里只有a是自由变量。因为b是参数c是局部变量所以它们都不是自由变量。

@cw84973570
Copy link

@cw84973570 cw84973570 commented Dec 12, 2020

又看了下文章,好像访问全局自由变量的是广义的闭包,实践角度的闭包是保存了外部作用域的局部变量的函数。

@xsfxtsxxr
Copy link

@xsfxtsxxr xsfxtsxxr commented Dec 18, 2020

@cw84973570 是的呀,如果用广义的所有函数都是闭包,实践中的闭包就是为了防止变量污染全局的变量,所以使用外层函数包裹内层函数,内层函数访问外层函数的局部变量,并且return出去,然后通过在外部申明变量的方式可以让外部的函数访问到其他函数的局部变量。(注意区分一下外层和外部,不是同一个东西)

function outer (){
 var a = 'outer'

 function inner (){
  return a
 }

 return inner()
}

function foo(){
 var b = outer()

 console.log(b) // 这里的b是outer函数的局部变量,不会污染全局,但是这个变量可以随处使用

 b = null // 但是过度使用不注意回收的话会导致内存泄露,因此在使用完以后置空
}

以上是我对闭包的理解,不一定准确,欢迎指正。😸

@cw84973570
Copy link

@cw84973570 cw84973570 commented Dec 19, 2020

@cw84973570 是的呀,如果用广义的所有函数都是闭包,实践中的闭包就是为了防止变量污染全局的变量,所以使用外层函数包裹内层函数,内层函数访问外层函数的局部变量,并且return出去,然后通过在外部申明变量的方式可以让外部的函数访问到其他函数的局部变量。(注意区分一下外层和外部,不是同一个东西)

function outer (){
 var a = 'outer'

 function inner (){
  return a
 }

 return inner()
}

function foo(){
 var b = outer()

 console.log(b) // 这里的b是outer函数的局部变量,不会污染全局,但是这个变量可以随处使用

 b = null // 但是过度使用不注意回收的话会导致内存泄露,因此在使用完以后置空
}

以上是我对闭包的理解,不一定准确,欢迎指正。😸

你这个是闭包?outer函数执行后整个作用域都销毁了,也就没有保存任何变量。

@xieyezi
Copy link

@xieyezi xieyezi commented Feb 24, 2021

时隔半年,再次来看,又一次醍醐灌顶。估计以后再也不会忘记了。理解记忆比死记硬背强太多了。

@zhanghang2017
Copy link

@zhanghang2017 zhanghang2017 commented Mar 10, 2021

“从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量”

老兄,感觉这两句话可以再推敲一下呢?
第一句,“即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)”
你的意思我应该明白了,但是给人一种创建它的上下文销毁可能销毁、可能没销毁的感觉。

第二句,在代码中引用了自由变量,那如果下面两种算不算闭包呢?
情况1:
var x = 1;
function f() {
return function () { console.log(x) }
}
var theFn = f()

情况2:
function f() {
var x = 1;
return function () {
return function() { console.log(x) }
}
}
var theFn = f()()

感觉情况1不属于我们通常理解的闭包,情况2又属于。

所以,能不能改成:

从实践角度:闭包是:
能够访问已经被销毁的执行期上下文中的活动对象的函数。
这两种都是属于闭包,只是第一种在自由变量是全局的执行上下文中

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet