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 · 134 comments
Open

JavaScript深入之闭包 #9

mqyqingfeng opened this issue Apr 27, 2017 · 134 comments

Comments

@mqyqingfeng
Copy link
Owner

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 commented Apr 27, 2017

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

@rivens-77
Copy link

rivens-77 commented May 10, 2017

请问下学长为什么

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

这里是3??怎么来的 为什么不是0,1,2

@mqyqingfeng
Copy link
Owner Author

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

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

@spicychocolate
Copy link

循环结束后

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

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

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

@mqyqingfeng
Copy link
Owner Author

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

@xdwxls
Copy link

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

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

@tangshuimei
Copy link

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

@tangshuimei
Copy link

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

@mqyqingfeng
Copy link
Owner Author

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

@mqyqingfeng
Copy link
Owner Author

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

@xdwxls
Copy link

xdwxls commented Jun 4, 2017

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

@mqyqingfeng
Copy link
Owner Author

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

@frankchou1
Copy link

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

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

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

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

@mqyqingfeng
Copy link
Owner Author

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

@Muscliy
Copy link

Muscliy commented Jul 20, 2017

var data = [];

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

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

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

@bighuang624
Copy link

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

@Muscliy
Copy link

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);
}

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

@keyiran
Copy link

keyiran commented Aug 20, 2017

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

@alicejxr
Copy link

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

@dengnan123
Copy link

学习了啊

@jasonzhangdong
Copy link

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

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

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

}
console.log(i) // 3

@shinchanZ
Copy link

大神,图挂了,可以更新一下吗

@xsfxtsxxr
Copy link

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

var foo = checkscope(); 
foo();

// 8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

// 想问一下:
// 1. 这里虽然f执行上下文弹出了,但是外部foo变量仍然引用着函数f,是不是函数f的作用域链依然存在内存中?必须foo = null才能彻底销毁?
// 2. 如果不用var foo,直接checkscope()(); 这样函数执行完毕就会完全销毁,不会留在内存中,是么?

@Leewansze
Copy link

Leewansze commented Nov 3, 2021

为什么在执行上下文中的例子里,是checkscope 和 f 都压入栈中,然后 f 先弹出执行

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

而在闭包的例子里,是 checkscope 执行完弹出后,才初始化 f 的

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

var foo = checkscope();
foo();

@mengzeze
Copy link

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

因为for循环中是用var定义的变量i,没有全局作用域,相当于把i当做全局变量。

@YorkWong30
Copy link

var data = [];

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

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

将var改成let后,能不能这样理解:
data[0]的作用域链顶端的活动对象(AO)存在i的值;

@YuFy1314
Copy link

YuFy1314 commented Jan 10, 2022

为什么在执行上下文中的例子里,是checkscope 和 f 都压入栈中,然后 f 先弹出执行

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

而在闭包的例子里,是 checkscope 执行完弹出后,才初始化 f 的

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

var foo = checkscope();
foo();

博主写的两个例子是不一样的, 上面的是弹出的f(), 执行完了弹出的, 在弹出checkscope, 而这次return的是f, 没有执行只是一个引用, 所以先弹出checkscope, 再执行foo之后弹出f

@gaowujie2004
Copy link

被 React 函数式组件 + Hook,按在地上摩擦了一顿。很有必要温馨加固一下:执行上下文栈,执行上下文,作用域链,闭包。

@lazylwz
Copy link

lazylwz commented Apr 1, 2022

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

var foo = checkscope(); 
foo();

// 8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

// 想问一下:
// 1. 这里虽然f执行上下文弹出了,但是外部foo变量仍然引用着函数f,是不是函数f的作用域链依然存在内存中?必须foo = null才能彻底销毁?
// 2. 如果不用var foo,直接checkscope()(); 这样函数执行完毕就会完全销毁,不会留在内存中,是么?

@xsfxtsxxr
回答第一个问题,当fContext从ECS中弹出时,此时还有一个全局上下文在栈底,如下,其中可以看到VO中的foo变量还保留着对函数f的引用。也就是f.[[scope]]存在与内存中,此时只有foo = null 可以销毁或者关闭页面。

ECStack = [
    globalContext,
]
globalContext = {
    VO: {
        scope: 'global scope';
        foo: reference to function f(){} 
    },
    scope: [globalContext.VO],
    this: window
}

回答第二个问题,checkscope()返回的函数没有被其他东西占用,但是还是需要再执行一次,所以暂时不销毁,当返回的值执行完成后,浏览器会在空闲的时间把它销毁。

@ghost
Copy link

ghost commented Apr 11, 2022

好文,之前每次面试都看到这篇文章,理解得很模糊,现在再看清晰了很多!

@wangpeng1994
Copy link

《你不知道的JavaScript》 中一句话:

对创建时所在作用域的引用。

@PointerToNextPole
Copy link

文章的第一句话

MDN 对闭包的定义为:

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

感觉很妙、言简意赅,但找了半天(包括 archive.org ),都没找到 MDN 上有这句话;直到看了下时间 2017 年 4 月,继续在 archive 里往前找,终于找到了:https://web.archive.org/web/20170116063418/https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures 。分享出来,以飨各位同好 🥳

@xyw1229
Copy link

xyw1229 commented Aug 14, 2022

MDN: 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

@Sakura-echos
Copy link

可以请问下, 下面这段代码

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

相当于下面哪种代码
第一种 :

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

第二种 :

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

@Sakura-echos
Copy link

如果是第一种的话, 那么下面这段代码

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

就会变成

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

那么当执行data0的时候, 这里面的i值是哪来的

      data[0] = (function (i) {
        return function () {
          console.log(i);
        };
      })(i);

@Kakarotto1026
Copy link

@Sakura-echos 在这个立即执行函数中,首先它会马上执行,并且i作为实参被传进来了,你可以理解为立刻把i"变现"成了对应的0,1,2。

@DaphnisLi
Copy link

var data = [];

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

data0; // 0
data1; // 1
data2; // 2
所以说改成 let,for 循环内的执行上下文就有 i 了。

@DaphnisLi
Copy link

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

老兄,感觉这两句话可以再推敲一下呢?
第一句,“即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)”
你的意思我应该明白了,但是给人一种创建它的上下文销毁可能销毁、可能没销毁的感觉。
第二句,在代码中引用了自由变量,那如果下面两种算不算闭包呢?
情况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又属于。
所以,能不能改成:

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

感觉应该立即成,虽然执行上下文被销毁,但作用域链里仍然保存着相关信息

@DaphnisLi
Copy link

如果是第一种的话, 那么下面这段代码

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

就会变成

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

那么当执行data0的时候, 这里面的i值是哪来的

      data[0] = (function (i) {
        return function () {
          console.log(i);
        };
      })(i);

你要仔细观察上下文链中各个父级上下文中变量的情况

@zsyoo
Copy link

zsyoo commented Mar 10, 2023

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

第一句说,即使上下文已经销毁,它仍然存在
这个表述似乎有点歧义,下面这种也属于闭包,但是上下文销毁的时候,闭包也销毁了
个人觉得这个不是必要条件,只是一种情况而已?
请问是这样吗?

        function fn1() {
            var num = 10;
            function fn2() {
                console.log(num);
            }
            fn2();
        }
        fn1(); //输出结果10

@1939108122
Copy link

我在《你不知道的JavaScript》上卷47页中看到:“在定时器、事件监听器中... 只要使用了回调函数,实际上就是在使用闭包”,那请问这段代码是闭包吗?(虽然使用了回调函数,但是timeHandler这个函数并没有被一个外层函数包围,它能记住的只有全局作用域的变量)
var name = 'jack';

setTimeout(function timeHandler(){
console.log(name);
}, 100);

@guava3s
Copy link

guava3s commented Mar 13, 2024

fContext在初始化时得到的作用域链会保存他的所有父变量对象,那么此时checkscope()函数的执行上下文对象已经出栈了,那么在foo()函数创建时的作用域链应该是两个全局变量对象啊?怎么还会引用到checkscope()的变量对象呢?

我自己一个理解是在var foo = checkscope(); 时,checkscope()虽然出栈了,但是foo 引用的还是 f 的地址,只要有存在地址引用,尽管无法直接在外层调用f,但f依然存在于内存中,则 f 所引用到的变量地址也都存在,也可访问。

但这又如何用作用域链来解释呢?还望大大指点

@xyhuangjia
Copy link

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

第一句说,即使上下文已经销毁,它仍然存在 这个表述似乎有点歧义,下面这种也属于闭包,但是上下文销毁的时候,闭包也销毁了 个人觉得这个不是必要条件,只是一种情况而已? 请问是这样吗?

        function fn1() {
            var num = 10;
            function fn2() {
                console.log(num);
            }
            fn2();
        }
        fn1(); //输出结果10

fn2(); 不需要 return么?

@rainbowyy
Copy link

rainbowyy commented Sep 23, 2024 via email

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

No branches or pull requests