• 蓝配鸡
    2019-08-27
    var bar = {
        myName:"time.geekbang.com",
        printName: function () {
            console.log(myName)
        }
    }
    function foo() {
        let myName = " 极客时间 "
        return bar.printName
    }
    let myName = " 极客邦 "
    let _printName = foo()
    _printName()
    bar.printName()


    全局执行上下文:
    变量环境:
    Bar=undefined
    Foo= function
    词法环境:
    myname = undefined
    _printName = undefined

    开始执行:
    bar ={myname: "time.geekbang.com", printName: function(){...}}

    myName = " 极客邦 "
     _printName = foo() 调用foo函数,压执行上下文入调用栈

    foo函数执行上下文:
    变量环境: 空
    词法环境: myName=undefined
    开始执行:
    myName = " 极客时间 "
    return bar.printName
    开始查询变量bar, 查找当前词法环境(没有)->查找当前变量环境(没有) -> 查找outer词法环境(没有)-> 查找outer语法环境(找到了)并且返回找到的值
    pop foo的执行上下文

    _printName = bar.printName
    printName()压bar.printName方法的执行上下文入调用栈

    bar.printName函数执行上下文:
    变量环境: 空
    词法环境: 空
    开始执行:
    console.log(myName)
    开始查询变量myName, 查找当前词法环境(没有)->查找当前变量环境(没有) -> 查找outer词法环境(找到了)
    打印" 极客邦 "
    pop bar.printName的执行上下文


    bar.printName() 压bar.printName方法的执行上下文入调用栈

    bar.printName函数执行上下文:
    变量环境: 空
    词法环境: 空
    开始执行:
    console.log(myName)
    开始查询变量myName, 查找当前词法环境(没有)->查找当前变量环境(没有) -> 查找outer词法环境(找到了)
    打印" 极客邦 "
    pop bar.printName的执行上下文




    展开

    作者回复: 分析步骤很详细 👍

     5
     35
  • 许童童
    2019-08-27
    思考题:
    这道题其实是个障眼法,只需要确定好函数调用栈就可以很轻松的解答,调用了foo()后,返回的是bar.printName,后续就跟foo函数没有关系了,所以结果就是调用了两次bar.printName(),根据词法作用域,结果都是“极客邦”,也不会形成闭包。
    闭包还可以这样理解:当函数嵌套时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局作用域下可访问时,就形成了闭包。

    作者回复: 分析的没问题

     2
     15
  • pyhhou
    2019-08-27
    思考题,最后输出的都是 “极客邦”,这里不会产生函数闭包,解释如下:

    1. bar 不是一个函数,因此 bar 当中的 printName 其实是一个全局声明的函数,bar 当中的 myName 只是对象的一个属性,也和 printName 没有联系,如果要产生联系,需要使用 this 关键字,表示这里的 myName 是对象的一个属性,不然的话,printName 会通过词法作用域链去到其声明的环境,也就是全局,去找 myName

    2. foo 函数返回的 printName 是全局声明的函数,因此和 foo 当中定义的变量也没有任何联系,这个时候 foo 函数返回 printName 并不会产生闭包
    展开

    作者回复: 分析的没问题 很赞

    
     10
  • 李艺轩
    2019-09-04
    关于闭包的概念:
    老师提出的概念:内部函数引用外部函数的变量的集合。
    高级程序设计中的概念:闭包是指有权访问另一个函数作用域中的变量的函数。
    MDN上的概念:闭包是函数和声明该函数的词法环境的组合。
    所以到底哪个是对的。。MDN = 老师 + 高程

    作者回复: 很高兴终于有人提这个问题了,我的观点是不要太纠结于概念,因为如何定义闭包不会影响到实际的使用,了解闭包是如何产生的,这才是本质的东西。

    
     5
  • 李懂
    2019-08-27
    本来对这篇文章充满期待,看完后还是有很多疑惑
    又翻看了一下小红书
    有以下疑问:

    1. 最后的分析图是不是有问题,全局上下文中变量环境怎么会有myName
    foo上下文中的innerBar是对象,用了函数?

    2.闭包是存在调用栈里的,现在的模块化存在大量闭包,那不是调用栈底部存在大量闭包
    很容易栈溢出吧
    3.看了下chrome中函数对应的[[Scopes]]是个List集合包含了闭包模块,这个是不是文章中的outer

    4.闭包是包含了整个变量环境和词法环境,还是只是包含用到的变量
    展开

    作者回复: 第一个我的疏忽,图明天改正过来。

    第二个问题:当闭包函数执行结束之后,执行上下文都从栈中弹出来,只不过被内部函数引用的变量不会被垃圾回收,这块内容要到讲v8 GC那节来讲了。

    第三个没明白意思

    第四个是 只包含用到的变量,这是因为在返回内部函数时,JS引擎会提前分析闭包内部函数的词法环境,有引用的外部变量都不会被gc回收。

     1
     4
  • hzj.
    2019-08-27
    首先两个函数都会打印 : 极客邦
    社区中对闭包的定义: 函数执行产生私有作用域, 函数内部返回一个调用的函数, 由于外部会拿到内部函数的返回值, 所以内部函数不会被垃圾回收, 这个私有作用域就是闭包.
    闭包的作用有两点: 1. 保护私有变量 2. 维持内部私有变量的状态
    但是在 sicp (计算机程序的构造与解释) 中认为: 只要函数调用, 那么就会产生闭包.
    所以, 我认为是会产生闭包的
    _printName() 输出 极客邦, 因为 _printName拿到了bar.printName, 打印上面的 myName即可.
    bar.printName() 输出 极客邦, 因为会直接打印全局的 myName.
    最后, 只有在 foo() 函数中有 log, 才会输出 "极客时间", 因为 这个值是在 foo 函数的私有作用域中的!!!
    展开

    作者回复: 分析的没问题,你把闭包的概念外延扩大也没问题,分析思路很赞

     1
     3
  • gigot
    2019-10-30
    var a=0;
    if(true){
     a = 1;
     function a(){};
     a=21;
     console.log(a)
    }
    console.log(a)
    老师,在 chrome 中,这里的 if 块语句有 a 函数,形成块级作用域了,所以函数 a 的声明被提升到 a = 1 之前;但是接下执行 function a() {} 的时候,全局的 a 的值却改变成 1 了, 导致最终输出为 21 和 1。想问下老师,这里面全局 a 是怎么改变的
    展开
     2
     2
  • Andy Jiang
    2019-10-11
    Local–>Closure(foo)–>Global,是不是表示setName执行时的执行上下文中的outer是指向闭包的?闭包中是否有outer指向全局执行上下文?
    
     2
  • YBB
    2019-09-18
    有几个问题,还想请老师解惑:
    1. 返回的内部函数在运行时,由于其外部函数的执行上下文已经出栈,其执行上下文中的outer指向何处?
    2. 如果函数嵌套产生多个闭包,是否也是类似于作用域链一样的机制,提供内部函数按序在闭包中查找变量?
    
     2
  • 忘忧草的约定
    2019-08-29
    老师我想请教一个问题:函数执行上下文是在函数执行前的编译阶段存入执行栈的、那么执行上下文中的outer也是在编译阶段通过分析函数声明的位置来赋值的吗?

    作者回复: 是的 编译阶段就确定了

    
     2
  • Marvin
    2019-08-27
    请问
    console.log(a)
    {
      function a(){}
    }
    为何会log一个undefined?目测function的变量提升会受到块的影响,这是标准浏览器的特性造成的,还是IE6时代就是这样呢?
    展开

    作者回复:
    这个问题我在前面回答过一次了,重新贴下:

    ES规定函数只不能在块级作用域中声明,
    function foo(){
        if(true){
            console.log('hello world');
            function g(){ return true; }
        }
    }
    也就是说,上面这行代码执行会报错,但是个大浏览器都没有遵守这个标准。

    接下来到了ES6了,ES6明确支持块级作用域,ES6规定块级作用域内部声明的函数,和通过let声明变量的行为类似。

    规定的是理想的,但是还要照顾实现,要是完全按照let的方式来修订,会影响到以前老的代码,所以为了向下兼容,个大浏览器基本是按照下面的方式来实现的:

    function foo(){
        if(true){
            console.log('hello world');
            var g = function(){return true;}
        }
    }

    这就解释了你的疑问,不过还是不建议在块级作用域中定义函数,很多时候,简单的才是最好的。

     2
     2
  • oc7
    2019-09-04
    function foo() {
        var myName = " 极客时间 "
        let test1 = 1
        const test2 = 2
        var innerBar = {
            getName:function(){
                console.log(test1)
                return myName
            },
            setName:function(newName){
                myName = newName
            }
        }
        return innerBar
    }
    var bar = foo()
    bar.setName(" 极客邦 ")
    bar.getName()
    console.log(bar.getName())

    有个问题没搞明白
    在return innerBar的时候 bar.setName(" 极客邦 ")和bar.getName()这两个函数还没有执行 为什么会执行词法作用域的分析 之前不是说只有函数调用时才创建这个函数的执行作用域和可执行代码
    展开

    作者回复: 这是预分析过程,主要是查看内部函数是否引用了外部作用域变量,用来判断是否要创建闭包,所以预分析过程并不是编译过程!

    
     1
  • ChaoZzz
    2019-08-27
    不会产生闭包,函数在被创建的时候它的作用域链就已经确定了,所以不论是直接调用bar对象中的printName方法还是在foo函数中返回的printName方法他们的作用域链都是[自身的AO, 全局作用域],自身的AO中没有myName就到全局中找,找到了全局作用域中的myName = ' 极客邦 ',所以两次打印都是“极客邦”啦~

    作者回复: 分析没问题,不过es6已经不用ao了,这块知识可以更新下了

    
     1
  • ytd
    2019-08-27
    不会产生闭包,都打印极客邦。printName函数定义时的执行上下文是全局,所以会在全局词法环境和变量环境下找myName。

    作者回复: 嗯,词法作用域是关键

    
     1
  • mfist
    2019-08-27
    1. _printName是一个全局函数,执行的话 不会访问到内部变量。输出全局变量的myName 极客邦
    2. bar.printName 同样输出是极客邦

    随着专栏的推进,发现看一遍文章的时间一直在增长。发现了很多的知识盲区,很多内容只是知道,不知道底层原理。

    今日得到:作用域链如何选择,闭包如何形成
    展开

    作者回复: 加油,词法作用域影响到作用域链,这点很关键

    
     1
  • 程力辛
    2019-08-27
    所以变量环境是动态的,根据函数调用关系。词法环境是静态的,根据函数定义时的状态?

    作者回复: 都是静态的,动态绑定的this下节内容讲,this系统和作用域链是两套不一样的系统

     1
     1
  • NikkiZeng
    2020-01-15
    老师,请问一下,闭包是只有一个,可以以不同调用方式传参修改,还是说每一次调用一次函数就会在执行上下文中产生不同的闭包呢?如果发生下面这种多层嵌套的时候,最里层的函数使用最外层函数的变量,那么是不是意味着三个函数,js垃圾回收机制都不会及时处理呢?

      function fun(n, o) {
            console.log(o);
            return {
              fun: function(m) {
                return fun(m, n);
              }
            };
          }
            var a = fun(0);
          a.fun(1);
          a.fun(2);
          a.fun(3);
          var b = fun(0).fun(1).fun(2).fun(3);
          var c = fun(0).fun(1);
          c.fun(2);
          c.fun(3);
    展开
    
    
  • 咖喱蟹
    2020-01-09
    李老师讲得真好,图例结合,很清晰明了,赞一个
    
    
  • Geek_East
    2019-12-08
    如果从定义拓展来看,可不可以说闭包是在作用域层面的,因为对象本身并不构成作用域,所以最后留的问题,其实是产生了闭包,不过变量是来自于global scope的。
    
    
  • Geek_East
    2019-12-08
    看了很多评论,有些疑惑的地方:
    闭包之所以没有产生是因为,闭包的界定在内部函数与外部函数直接的,而且闭包本身是在scope范围内的,跟执行上下文没有关系;在返回bar.printName的时候,myName的查找首先肯定不会是time.geekbang,com, 因为对象本身并不能成为scope,再往上一层查找的时候,根部不会查找到foo里面的极客时间,因为scope查找是静态的,根据代码位置决定的,所以就找打了global scope里面的极客邦。

    最关键的一点是:
    执行上下文的机制遵循stack LIFO原则,是动态的,是运行时的东西;标识符的查找则是通过代码字面上的位置进行查找,是静态的,是编译时的东西;这两种机制搞在一起,确实容易难倒很多人;但是只要分的开,就好多了;

    另外想问一个问题是:scope的这种寻找机制貌似是通过outer引用机制实现的,而且会从词法环境找到变量环境再横跨不同的执行上下问,这样理解可以吗?
    展开
    
    
我们在线,来聊聊吧