美文网首页软件Javascriptjs学习
前端基础进阶(四):详细图解作用域链与闭包

前端基础进阶(四):详细图解作用域链与闭包

作者: 这波能反杀 | 来源:发表于2017-02-13 03:26 被阅读33653次

由于某些原因,文章已经删除,打算迁移到别处,目前正在寻找更合适的平台。

请大家关注我的新公众号ar_indus,随后我会在公众号里推送新的博客地址。

后续计划的《react进阶系列》文章也会在新公众号中推送。

公众号二维码

ar_indus

相关文章

网友评论

  • 不净莲华:前文说到只有在上下文执行阶段也就是函数调用栈栈顶的上下文进入执行阶段,图中有两个AO也就是两个函数都在栈顶?
  • 不净莲华:关于作用域链为什么只有全局变量对象是VO而其他的是AO活动对象?
  • c42ed0842be2:作用域链中的所有vo和它们对应执行上下文创建时候的vo是同一份数据吗
  • 哦啦啦哈:它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。

    当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

    这条叙述中 这个执行上下文(代号A) 并不包括全局上下文 是吗?
    这波能反杀:@js新手 不包括
  • 567837fe9359:感谢你 真的讲得很好
  • f1482cbfb158:我说一下,我的理解吧,结合《JavaScript高级程序设计》和《JavaScript权威指南》对闭包的解释,我更觉得权威指南解释更为正确一些,闭包不应该说是指某个函数吧,波神你根据chrome的显示,说是外层函数,而高级程序设计恰好相反,说闭包是内部函数。闭包更准确的说是一项技术或者一个特性,函数作用域中的变量在函数执行完成之后就会被垃圾回收,一般情况下访问一个函数作用域中的变量,正常是无法访问的,只能通过特殊的技术或者特性来实现,就是在函数作用域中创建内部函数来实现,这样就不会使得函数执行完成变量被回收,这种技术或者特性应该被称为“闭包”,像是《JavaScript权威指南》打的比方,像是把变量包裹了起来,形象的称为“闭包”。我觉得应该是这样,而不是指某个函数是闭包。发表一下个人观点,有可能有错误,希望能抛砖引玉,引来大家更加深入的理解。
  • 一枚程序员的灵感:for (var i = 1; i <= 5; i++) {
    function time(index) {
    setTimeout(function () {
    console.log(index)
    },index*1000)
    }
    time(i)
    }
  • nineSean:执行上下文A,且在A下创建了函数B。当函数B调用时有访问到A的变量对象中的值,就产生了闭包。那在全局执行上下文中的函数声明和调用按照这样定义应该也是产生了闭包,但是在开发者工具中确实没有closure的,是否定义不够严谨呢?
  • abb584c5fc56:好喜欢作者的风格啊,哈哈
  • 烦浅:从网上看到的下面一段话:(题外话立即执行的匿名函数这不叫闭包吧?)
    闭包就是有权访问另一个函数作用域中变量的函数.
    分析这句话:
      1.闭包是定义在A函数中的B函数(体现在A函数内上下文将B函数赋值到A函数外部的变量中).
      2.闭包能访问A函数中的c变量(实际A函数外部不能直接访问c变量).
      3.即使A函数执行完了, 被闭包引用的c变量也得不到释放(A函数外部存在变量对B函数的引用,调用该引用即可访问c变量).
  • ImNotSure:“这里的标示符,指的是变量名和函数名。” 标示符不包括参数吗?:flushed:
  • 聚宝大当家:在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

    上面这一段没有太理解,大神能不能再通俗的指点一下,谢谢
    Promise__:就是查找一个变量时候在当前函数内部找该变量,找不着开始找他爹,也就是他外层的函数,找不着再往外找,一直找到全局,大概是这吧,对于这个理解不深,望指正
  • ab3cdc10dec7:问个问题哈~在闭包的第二个例子里,先有了全局EC,然后创建了 foo EC,下来是bar EC。最后在bar中调用了fn,这里会创建fn EC么?谢谢 ^ ^
    ab3cdc10dec7:@ivuu 中间少说了一步,创建bar EC之前,foo EC会先出栈
  • 石头上的话多:var c=9;
    (function(){
    var a = 3;
    (function(){
    var b = 4;
    function _closure(){
    console.log(b);
    console.log(a);
    console.log(c);
    }
    _CLOSURE=_closure;
    })()
    })()
    var _CLOSURE;
    _CLOSURE();

    这样相当于形成了两个闭包吗,chrome的scope里面确实有两个closure.抱歉,好像评论不能上传截图。
    这波能反杀:@庭户无声 以chrome里为准
  • 飘落的枫:我觉得闭包是在js初始化的时候已经确定了他的作用域,也就是所谓的静态作用域或者词法作用域,其实核心就是这个,另外还和垃圾回收机制有关
  • 0201dde59723:@波同学 你好,文章写得特别好,有一个问题我有点糊涂
    就是这里的call stack和执行上下文(execution context)的区别,比如是每个函数的execution context包含了对应的call stack呢? 还是对应的call stack包含了对应的执行上下文?
    0201dde59723:@这波能反杀 嗯嗯 好的,谢谢!
    这波能反杀:@派崔克韩 你在chrome里观察一下call stack应该就一下明白了
    这波能反杀:@派崔克韩 ... 往后面看吧。文章里应该有说的
  • 床上功夫得过奖:有点搞不清楚了,“作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问”。作用域是在编译期的概念,变量对象是执行期的概念,先有编译,再有执行,那作用域链怎么就是执行期生成的变量对象组成呢?

    PS:前几篇都看了,写的真好!
    这波能反杀:@床上功夫得过奖 作用域就是一套规则,作用域链是这套规则的具体实现。但是最近新版本的chrome里,函数在声明时也包含了作用域链,所以建议你先别纠结这个了
  • 5a1e62cd54e9:var fn = null;
    function foo() {
    var a = 2;
    function innnerFoo() {
    console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误
    console.log(a);
    }
    fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
    }

    function bar() {
    var c = 100;
    fn(); // 此处的保留的innerFoo的引用
    }

    foo();
    bar();
    建议作者在这个例子后面补充 词法作用域相关知识,这样方便理解
  • a52f14f2ca33:执行上下文到底是什么东西 是个什么概念
    Promise__:看这一系列前两篇文章
  • a6f640d0946a:var a = 20;

    function test() {
    var b = a + 10;

    function innerTest() {
    var c = 10;
    return b + c;
    }

    return innerTest();
    }

    test();

    在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。

    上面的说法是否有误呀?
    因为test中的函数innerTest在test的可执行代码中,并没有被调用执行,因此执行test时,innerTest不会创建新的上下文?
  • 度假中的赵子龙:for (var i=1; i<=5; i++) {
    function timer() {
    var num = i;
    function innerTimer(){
    console.log(num);
    }
    return innerTimer;
    }
    var result = timer();
    setTimeout(result , i*1000 );
    }
  • de652f185a4e:"很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容"

    var a = 20;

    function test() {
    var b = a + 10;

    function innerTest1() {
    var c = 10;
    return b + c;
    }
    function innerTest2(){
    ......//
    }

    return innerTest();
    }

    test();

    如果test()里面有多个子函数,就不是起点指向终点了吧,test包含多个子函数,innerTest1与innerTest2是同一作用域吧,理解起来还是包含的味道


    Promise__:那可以理解为两条单向通道。。
  • 昔人已老:es6中有新增块级作用域哦
  • 流水萧尘:波波老师写的很赞,这里面遇到一点小问题,关于闭包的概念,看高级程序定义的是有权访问另一个函数作用域中的变量的函数,按照这个理解的话,您本文这句话,“简单来说,假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。”,是不是应该说A是一个闭包呢?
    Promise__:@zeusiqd 这里是按chrom来理解的
    zeusiqd: @流水萧尘 他这片文章有点问题,和高城和mdn都不一样
  • lolivialucky:var a = 20;

    function test() {
    var b = a + 10;

    function innerTest() {
    var c = 10;
    return b + c;
    }

    return innerTest();
    }

    test();
    这个函数里面 innerTest()并没有执行啊 也会生成执行上下文么
  • 四爷在此:for (var i=1; i<=5; i++) {
    // immediately register returned function a..
    setTimeout((function (){
    var b = i;
    console.log('b: ' + b);
    return function a() {
    console.log(b);
    }
    })(), i*1000 );
    }
    波哥,来了。。自己再理一下,其实timeout函数把要执行的函数注册好了,但是打印函数执行时,this已经指向window,window.i 已经是最大值。。所以可以这么理解,要借助Closure保存不同时期的i 值,那就用一个私有变量 b=i 放到一个函数作用域中,返回打印函数,函数保留了对私有变量的访问。其实是保留了好几个匿名函数作用域,不知道我理解的对吗
  • 泡沫的快乐:!function(){
        let o = {
            test:1
        };
        let val = o.test;
        let a =Object.defineProperty(o, 'test', {
            get(){
                return val
            },
            set(newVal){
                val = newVal;
                return newVal
            }
        });
        window.o = o;
    }()
    console.log(o)
    console.log(o)
    这里的 IFEE 算不算闭包? IFEE运行完后,属性在用set,get,的时候 还是可以访问到val,我觉的算。
  • da7d39373811:js预编译阶段,不是生成活动对象吗。但是我看老师的文章中是在js执行阶段创建执行上下文。且在执行上下文中创建活动对象。
  • DHFE:波波老师,我想问,如果闭包直接返回的是父函数内部的变量,但没有函数的引用给外界,这还算不算闭包?
    这波能反杀:@这个嘛 往后看
  • coderLfy:非常经典,每日站在地铁上,反复研读波同学的文章,让我希望满满啊~
  • d9c9b613a727:请问那个画图软件是什么?谢谢
  • df1b85e0c408:谢谢前辈的分享,不过我有个疑问:这个例子
    var a=15;
    function bar(){
    console.log(a);
    }
    function foo(){
    var a=10;
    bar();
    }
    foo(); //15;
    这是否说明了作用域链是在代码编译是确定的?
    这波能反杀:@sterchois 我文章应该有说
    df1b85e0c408:@波同学 以上面的例子是否可以说明作用域链其实是在代码编译是确定的??前辈能解答一下吗?
    df1b85e0c408:对于文中举的例子,我改成这样:
    var a = 20;

    function innerTest() {
    var c = 10;
    return b + c;
    }

    function test(){
    var b = a + 10;
    innerTest();
    }

    test(); //ReferenceError: b is not defined
  • ilikes:这么好的文章才10个赞赏?大家收获了就该赞赏啊。而且默认才2元
    顺便问下作者哪里能找到ES的最新信息、包括编译器的一些知识。
    简单说就是ES的官方网址是哪里。多谢
  • L小庸:你好,看了你的文章感觉很受益,但建议把参考资料在文末注明,比如在基础进阶这几篇文章中应该是对《You Don't Know JS (book series)》里面的内容有很多借鉴,这样读者如果想了解更多信息会更方便。
  • 61ca2438a691:如果我们在函数bar中声明一个变量c,并在闭包fn中试图访问该变量,运行结果会抛出错误。

    这个应该是跟js是静态作用域有关吧!只跟程序的定义的位置有关
  • 343b187af836:在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。请问这句话怎么理解呢?
  • 爱思考的蛋:想问问楼主:num1 = !!num1 ? num1 : a;这句话是什么意思?还有window.add=add是给全局添加一个属性add,然后把add函数赋值给它吗?
  • 5d6055729b64:看了您的第三篇文章变量对象详解和第四篇作用域链和闭包详解;
    最开始学JS的时候从网上的一些文章中了解到js代码的整个执行过程分为预编译阶段和执行阶段,大概讲的是预编译阶段会创建所有var的变量并赋值为undefined,创建所有的函数引用;执行阶段会进行变量的赋值,然后执行代码。
    看完您的文章后我有两个疑问:1以前所说的js预编译阶段和执行阶段指的其实是全局环境的执行上下文的创建阶段和执行阶段;2按照您第四篇文章中对js代码执行过程的区分,我以前所看到的这种“预编译阶段和执行阶段”的观点指的其实都是您文中所描述的执行阶段,他们对应的是全局环境的执行上下文的创建和代码执行阶段;
    哪一个才是正确的观点呢
    这波能反杀:@fclive 信我就行
  • 回归线_3c5b:作用域: 定义了一套执行规则 (在代码编译阶段)
    作用域链: 一系列的vo(在代码执行阶段的 第一个生命周期 创建阶段)
    自由之思想独立之人格:(二)中有云:每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域
  • 尤小小:让读者老爷们看了之后。。。 看到这句我就不高兴了,你的读者只有老爷们吗?😭
    尤小小:@波同学 😁
    这波能反杀:@胖脸猫 :joy: 还有读者小姐姐
  • dc0cf3f0be58:波波老师你好,昨天无意间发现了你的文章,特意注册了简书,一口气看了你好几篇文章。在看本篇文章时发现一个问题,在你讲闭包的第一个应用时(setTimeout),举的例子:
    function fn() {
    console.log('this is test.')
    }
    var timer = setTimeout(fn, 1000);
    console.log(timer);
    这里你也说了 “执行上面的代码,变量timer的值,会立即输出出来,表示setTimeout这个函数本身已经执行完毕了。”也就是说setTimeout函数的执行,是在console.log(timer)之前。但是你在另一篇文章中(详解那道setTimeout与循环闭包的经典面试题),又说 “而这个队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由setTimeout定义的操作”,按照这样说的话,应该是setTimeout的执行在console.log(timer)之后才对啊。
    我不知道是自己哪里理解有误,还请波波老师指教!
    dc0cf3f0be58:@波同学 我又看了一遍后面那句话,好像意思应该是说所有可执行代码执行完毕之后,才会开始执行fn,确实搞错了。:grin:
    这波能反杀:@Andyang 你没理解清楚setTiemeout函数本身和他的fn的区别,你把他们混淆了
  • 64992ad017f4:请问一下,执行环境栈在内存中是真实存在的吗?存在栈中吗?
  • a9d2c52f5e13:function fn() {
    console.log('this is test.')
    }
    var timer = setTimeout(fn, 1000);
    console.log(timer);// 这里为什么会是1?
    Promise__:timeID
  • 5b5788d14726:for (var i = 1; i <= 5; i++) {
    setTimeout((function timer() {
    console.log(i);
    })(i), i*1000 );
    }
    我想问我这么做有用到闭包吗?
    这波能反杀:@演绎神话 看我后面的文章
    5b5788d14726:还有就是明明这里是不同的延后执行时间,为什么我这个定时器它把这五个数同时执行出来了
    @波同学,麻烦你帮我理清一下思路,谢谢了。
    5b5788d14726:@波同学 ,请看一下我的评论
  • 人世间:简单来说,假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。

    这个解释不准确。简而言之闭包就是运行时能访问另外一个函数内的变量的函数。例如:function A(){ var a="aa"; return function B(){alert(a)}}; funcA = A()。A和B都不算是闭包,A和B组合成构造了闭包环境,真正的闭包是funcA。即A函数包变量a封闭起来了,外面无法访问,但是提供了一个包B函数可以访问。A和B一闭一包。上面那句话可以解释为: 闭包就是运行时(funcA())能访问另外一个函数(A)内的变量(a)的函数(funcA)。
    人世间:@波同学 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
    这波能反杀:@人世间 就是一直没搞清楚闭包应该怎么解释,都一直没找到哪里有一个比较明确的说法
  • 夏目祐太:假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。
    在我的理解A才是闭包吧。
    zeusiqd: @关于郑州想的全是你 他是按chrome的理解
  • 646d09b1e4e6:波波老大,i*1000这个我感觉延迟都一样啊
    这波能反杀:@easterCat 你可能哪里出了问题,看看后面的文章吧
  • 三生三世小世界: var num1 = !!num1 ? num1 : a;
    var num2 = !!num2 ? num2 : b;
    这两句话怎么读,没看懂
  • 三生三世小世界:var a = 20;

    function test() {
    var b = a + 10;

    function innerTest() {
    var c = 10;
    return b + c;
    }

    return innerTest();
    }

    test();
    不是return innerTest()了吗,为什么还会创建innerTest()的上下文
    三生三世小世界: @L小庸 你是不是回复错了
    L小庸:你好,看了你的文章感觉很受益,但建议把参考资料在文末注明,比如在基础进阶这几篇文章中应该是对《You Don't Know JS (book series)》里面的内容有很多借鉴,这样读者如果想了解更多信息会更方便。
    三生三世小世界:函数加括号表执行,不加括号就不执行,
    rturn innerTest():innerTest是执行了的。
    return innerTest:innerTest是未执行的,返回的只是innerTest的引用地址
  • 28b9797224a8:调用函数的时候,执行上下文创建,此时包含了变量对象的创建,
    function compare(value1, value2){
    if(value1 < value2){return -1;}
    else if(value1 > value2) {return 1;}
    else{return 0;}
    }
    var res = compare(5, 10);
    以这个为例子吧:
    此时VO={
    arguments:[value1, value2]
    //没有变量和函数
    }
    到执行上下文的执行阶段VO变成AO
    AO = {
    arguments: {value1: 5, value:10}
    }
    是这样吗?主要是想知道是不是
    VO时,arguments只有属性名,属性名就是参数名;
    然后执行阶段AO,arguments就有了属性名和属性值,因为函数值传进来了?
    还是VO,AO都是arguments:{value:5, value: 10}??
    这波能反杀:@嬡莪莂趉 就是VO啊
    28b9797224a8: @嬡莪莂趉 能说详细点吗😊谢谢
    这波能反杀:@嬡莪莂趉 VO就有值
  • 28b9797224a8:但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来;
    这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。
    能不能再解释一下这一部分?
    fn=innerFoo,不是只是复制了innerFoo函数的地址给全局变量fn?难道复制是复制整个变量对象?
    setTimeout这部分描述,应该原因是相似的吧?
    谢谢
    28b9797224a8:@波同学 后来看了一下书,大概懂了一点;就是因为fn被innerFoo的作用域链初始化,也就是说fn具有了innerFoo的作用域链,这个作用域链包含了innerFoo本身的活动对象,foo的活动对象,以及全局活动对象,所以因为fn具有这个作用域链,使得本应该被销毁的innerFoo被保留下来了;
    这波能反杀:@嬡莪莂趉 只是引用
  • 744bb97e6c1a:请问一下那个模块例子中的num为什么要取反两次?直接三元判断不就行了吗
  • f1a61fc0cb04:对上面的例子稍作修改,如果我们在函数bar中声明一个变量c,并在闭包fn中试图访问该变量,运行结果会抛出错误
    所以说闭包到底是哪个? @波同学
    自由之思想独立之人格:我也有这个疑问
  • 夏目祐太:for (var i = 1; i <= 5; i++) {
    (function(i) {
    setTimeout(function() {
    console.log(i);
    }, i * 1000);
    })(i)
    }

    使用自执行来解决setTimeout带来的问题
  • 6b5a2e2b487a:老师,我还是不知道其中的原理,我真是瓜皮
    6b5a2e2b487a:老师能否一语点醒瓜皮猪
    6b5a2e2b487a:加了自执行函数的话,为什么又能一边赋值一边加
    6b5a2e2b487a:不加自执行函数的话,为什么全局变量先加完再给setTimeout赋值
  • c1c38f6f74eb:JavaScript不是解释型的语言嘛,也有编译阶段吗?
    c1c38f6f74eb:@波同学 :+1:
    这波能反杀:@lIrUIl0nG 现在已经没有那么明确的界定了,为了提高性能,在实现上仍然会进行编译,这个过程一般只有几十ms的样子
  • aaa790849e4b:function a(){
    var i=0;
    function b(){ return (++i); }
    return b;
    }

    var c=a()();
    alert(c);//输出多少呢
    多次调用a()()呢
    @波同学
    这波能反杀:@Forest_Ho 就是运行函数b的意思
    aaa790849e4b: @波同学 每次调用a()()都是输出1,这个a()() 代表什么意思呢?
    这波能反杀:@Forest_Ho 你动手试试看就知道了
  • 22d41e3eba93:想问一个问题,看完后我的理解是js代码执行总的过程应该包含编译阶段和执行阶段,其中执行阶段中又包含变量对象等等的创建过程和代码正式执行过程,执行阶段的你已经讲了很多了,那编译阶段的具体是发生在什么时候?是代码执行前的微小时间内去编译当前要执行的代码?还是整个代码文件引入时会整体编译?比如:
    var foo = 'foo';
    (function () {
    function bar() {
    var a = 1;
    console.log(a);
    }
    function baz() {
    var b = 1;
    console.log(b);
    }
    bar();
    baz();
    })();
    console.log(foo);
    上面代码的baz的编译阶段是发生在baz()执行之前,bar()执行之后,还是刚开始就bar,baz方法一起编译了,因为网上又有人说js是解释型语言,是边编译边执行的,没太搞懂这句话,有点混乱。希望解惑下:pray:
    22d41e3eba93:@波同学 噢 明白了 3Q🙏
    这波能反杀:@凌子亦 解释型语言并不是说边编译边执行,编译与执行也是有先后的。而是当我们要运行的时候才开始编译。只不过JavaScript的编译过程非常短,肯定是在执行阶段之前编译的
  • 56322fe44873:之前看过一篇文章说了setTimeout的传值方式,试了一下发现不用添加闭包也可以把值传进去是用.第三个参数+是传入惨
    ```javascript
    for (var i=1; i<=5; i++) {
    setTimeout( function timer(i) {
    console.log(i);
    }, i*1000 ,i );
    }
    ```
    这波能反杀:@黑色技术 嗯,是的
  • 饥人谷_xxxxx:波老师,您讲的好棒,特别细致入微,请问下您画图用的是什么工具啊?谢谢啦!
    这波能反杀:@饥人谷_Mcavoy processOn
  • 6536e0a782f4:"但是通过fn = innerFoo,函数innerFoo的引用被保留了下来"这时候的innerFoo应该还没有执行上下文,但是已经有引用了吗?
    6536e0a782f4:引用是什么时候创建的呢?
  • 洲行:作用域链 可以被改变么?
    洲行:@波同学 怎么改啊
    这波能反杀:@舟舟周 可以
  • chenpipy:波波老师,有个疑问,闭包产生的条件是:当函数可以记住并访问所在的作用域(全局作用域除外)时,就产生了闭包,那么可以记住的条件是什么呢?本例中的条件是函数的引用赋值给了全局变量,调用该全局变量,触发了闭包;而setTimeout则是将函数保存在setTimeout变量对象中,定时调用函数时,触发闭包(本章中最后一题中,正是因为函数在全局作用域中,所以没用触发闭包,5楼答案很赞,思想就是将该函数放到局部作用域中,这样理解对吧);那么问题来了,还有其他的触发条件形成闭包吗(排除例子中的两种触发方式)?
    这波能反杀:@chenpipy 你这个问题问得很好啊。我想想,应该是当内部函数存在访问上层函数或者存在访问可能性的时候,就能够记住上层作用域。
  • 6536e0a782f4:“注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active Object” 为什么VO(global)这儿不是AO呢?而是VO
  • csRyan:'use strict';

    function test() {
    if (true) {
    function fun() {
    console.log("in fun!");
    };
    fun(); // in fun!
    }
    fun(); // 报错:fun is not defined
    }
    test();
    请问这个怎么用作用域链解释呢?
    这波能反杀:@csRyan 第六章有方法
  • _Josh:问个题外话 图是什么软件画的:sweat_smile: :yum:
  • 2241c572b526:var loop = function() {
    return function(i) {
    setTimeout(function() {
    console.log(i);
    }, i*1000)
    }
    };

    for (var i = 1; i <= 5; i++) {
    loop()(i);
    }

    這麼做也行 就是有點複雜
  • YUKI酱不要打酱油:提一个错别字。。。“之所有有这个疑问”,应该是“之所以”。。
    这波能反杀:感谢指正
  • 沁浒浒浒浒浒浒:for (var i = 1; i <= 5; i++) {
    (function(i) {
    setTimeout(function timer() {
    console.log(i);
    }, i * 1000)
    })(i);
    }

    可否理解成
    AO1 = {
    arguemnts: {
    i: 1
    }
    }
    setTimeout(function timer() {
    console.log(AO1.arguments.i);
    }, AO1.arguments.i * 1000)

    AO2 = {
    arguemnts: {
    i: 2
    }
    }
    setTimeout(function timer() {
    console.log(AO2.arguments.i);
    }, AO2.arguments.i * 1000)

    ...
    这波能反杀:可以。
  • 226b00823416:作者欧巴你好棒!还有,读者也可以不是老爷们儿还有程序媛妹纸
    这波能反杀:嗯,这个知道,做前端的很多程序媛妹纸:stuck_out_tongue_winking_eye:
  • 我就不信这个昵称也不合法:for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
    console.log(i);
    }, i*1000 );
    }
    这个我还是不能理解,有没有大神能够细致的讲解一下。跪求
    Promise__:循环结束时其实距for循环开始执行仅过去了非常短的 时间 ,不好意思打错了~
    Promise__:@我就不信这个昵称也不合法 首先,js是单线程执行的,在任何一个时间点,有且只有一个线程在运行js程序,无法再同一时间运行多段代码。在这里,for循环条件语句中的 i 其实相当于在全局中先 var i ;然后在条件语句中赋初值等于1,开始循环。循环执行第一次,i=1,然后js引擎在遇到setTimeout定时器时会将这个setTimeout插入队列中等候,然后for循环执行第二次,i 被重新赋值为2,同样再将第二个setTimeout插入队列中等候,依次循环,等到第五次循环开始,i=5 , 将第五个setTimeou插入队列中等候,然后接下来 i 会先自增一下,i = 6,再去for循环中条件语句判断,此时 i=6 不符合条件 i<=5,所以跳出循环,此时 i 值为6。 循环结束时其实距for循环开始执行仅过去了非常短的事件,因为for循环语句执行速度较快,但此时 i = 6 ,接下来才是setTimeout 队列的执行时间,由于循环执行5次,因此setTimeout队列中会有5个需要执行的setTimeout代码块,首先执行第一个setTimeout,输出6,隔一秒后,开始第二个,依旧输出为6,总共输出5次6
    我就不信这个昵称也不合法:为什么全部输出为6
  • 我就不信这个昵称也不合法:稳重提到 “通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。”,比如闭包第一个demo中bar,那么函数bar的执行环境中有哪些东西?
  • 61ca2438a691:var fn = null;
    function foo() {
    var a = 2;
    var b = 3;//新增的

    innnerFoo(); // 将 innnerFoo的引用,赋值给全局变量中的fn
    function innnerFoo() {
    console.log(a);
    }
    }
    foo();
    对你的demo略作调整,当我断点到innnerFoo内部的console时,在cosole中访问b为啥访问不了?
    根据你上面的解释 “这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。” 理论上应该可以访问的呀。求解释。
    61ca2438a691:是我什么地方理解偏差了吗?
  • 淘淘笙悦:你的文章我看了好几遍,觉得真是有一种顿悟的感觉,闭包这我刚又看了一遍,发现最后一道题总是不太理解,不知能不能出个教程讲解下原代码和修改后的代码的理解呢。
    淘淘笙悦:@波同学 哈,好的,辛苦了,期待。
    这波能反杀:好,没问题,下一篇补充。顺便解释一下setTImeout的队列执行问题。
  • 越夜鸣:var color = 'red';
    function changeColor(){
    var anotherColor = 'blue';
    function swapColors(){
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
    }
    swapColors();
    }
    changeColors();
    这段代码来看,全局环境上下文里的->changeColor上下文里的->swapColors里面调用了全局上下文的color属性,这样隔了一层上下文环境,可不可以说swapColors就是相对于全局环境的一个闭包?:sunny:
    越夜鸣:@波同学 哦 got it!3Q!
    这波能反杀:@越夜鸣 如果函数内部访问了全局变量,是不会形成闭包的。如果函数内部访问了上层函数的变量,上层函数就可以称之为一个闭包。所以changeColor是闭包的原因,是因为swapColor访问了changeColor的变量
  • 越夜鸣:var index = null;
    for(var i = 1; i <= 5; i++) {
    (function (){
    var index = i;
    setTimeout(function timer() {
    console.log(index);
    }, i * 1000);
    })();
    }

    哎呀我去,今天认真来看 不小心就过了12点了
  • 97900e44363d:波哥,想问一下,innnerFoo被保存进全局变量fn中后,那fn的作用域链== innnerFoo的作用域链。请问是这样吗?
    97900e44363d:@波同学 好的,谢谢波哥,主要没想通,如果作用域链没变的话,那fn是在哪里找到innnerFoo的属性的呢?是因为闭包导致innnerFoo的变量对象保留了下来吗?
    这波能反杀:闭包不会改变作用域链,你可以读第六章的内容,然后学着在浏览器里查看他们各自的作用域链变了没
  • 年轻小子:function fn() {
    console.log('this is test.')
    }
    var timer = setTimeout(fn, 3000);
    console.log(timer);
    web-9fe947c….js:10 13
    undefined
    web-9fe947c….js:10 this is test.

    不理解那个输出的那个13
    年轻小子:@波同学 原来这个样子啊 谢谢了
    这波能反杀:那是setTimeout的一个数字标识,不同的setTimeout标识不一样
  • 不吃早餐我就是Mark:理解起来有点问题,我说下问题,麻烦看下理解的正不正确:
    for(var i = 1; i <=5; i++){
    setTimeout(function timer(){
    console.log(i);
    }, i * 1000);
    }

    对于上述代码的理解:
    i在1000ms内早已变成了6,所以会console.log出5次6,但是此时有一个疑问,不考虑let,setTimeout是怎么记录下当前的i值呢? 为什么不会到5s的时候瞬间 console.log 5次6呢?

    for(var i = 1; i <=5; i++){
    (function (i){
    setTimeout(function timer(){
    console.log(i);
    }, i * 1000);
    })(i);
    }

    还有第二个问题:
    上述代码使用IIFE,可以理解,保持i的值,但是闭包的定义严格来说应该不是IIFE而是timer函数定义在setTimeout作用域外而在该作用域内执行了? (代码未体现timer定义在作用域外。)
    这波能反杀:@不吃早餐我就是Mark 对
    不吃早餐我就是Mark:@波同学 好像有点理解了,在在IIFE环境内定义,作用域链其实也是正常的,只不过在setTimeout内执行,阻止了IIFE的垃圾回收机制,从而保留了i的值?
    这波能反杀:闭包timer,在IIFE环境内定义,在setTimeout的内部机制中执行,这是符合定义的,你只是没想明白setTimeout他也是一个函数,也有自己的执行环境
  • 07d09d5b6dca::smile: 个人拙见,感觉闭包的定义加上几个字会更好理解一些:
    “当一个函数可以记住并访问其被创建时所在的作用域(全局作用域除外),并在定义该函数的作用域之外执行时,该函数就可以称之为一个闭包。”
    这波能反杀:@月半羊 赞
  • 87335dfa8d4a:对于这个例子,我有所保留。

    function test() {
    function bar (str) {
    console.log(str);
    }

    function foo (fn, string) {
    fn(string);
    }

    foo(bar, 'this is closure');
    }


    test();


    “先直截了当的抛出闭包的定义:当一个函数可以记住并访问所在的作用域(全局作用域除外),并在定义该函数的作用域之外执行时,该函数就可以称之为一个闭包。

    简单来说,假设函数A在函数B的内部进行定义了,并在函数B的作用域之外执行(不管是上层作用域,下层作用域,还有其他作用域),那么A就是一个闭包。记住这个定义,你在其他地方很难看到了。”

    这个定义出处是什么地方?

    https://www.w3schools.com/js/js_function_closures.asp 底部黄色部分
    “A closure is a function having access to the parent scope, even after the parent function has closed.”

    后半部分决定了这个例子是否闭包。

    也可以看看这个

    http://stackoverflow.com/questions/1801957/what-exactly-does-closure-refer-to-in-javascript

    http://javascriptissexy.com/understand-javascript-closures-with-ease/
    这里也有相关的关键点
    1、函数内部访问了父级作用域上的东东
    2、函数可以在父级执行完毕后依旧存在,仍然可以执行

    但对于上面1这点,我曾经试过在内部函数挂eval,然后在外部传入javascript字符串,然后获取外部作用域的东东,实测是可行的。

    所以对于闭包这个定义,我还是没找到一个完整的。

    希望继续和你探讨。:smile:

  • 7acd74e95113:必须赞!
  • Samhanx:for (var i=1; i<=5; i++) {
    function timer(i) {
    setTimeout( function() {console.log(i);}, i*1000 );
    }
    timer(i);
    }
    波哥这个如何?
    这波能反杀:good,可以直接使用自执行的方式会更好一点
  • kerush:function timer(i) {
    console.log(i);
    }


    for (var i = 1; i <= 5; i++) {
    (function(i){
    setTimeout(timer(i), i * 1000);
    })(i)

    }


    我这么一折腾,全都一下子出来了,和五楼兄弟还是区别有点大啊
    这波能反杀:在声明timer的时候,你把参数传进去了,而且函数的作用域链并不会因为闭包改变
  • 42eccce13f4b:我是来找碴的。
    “JavaScript中只有全局作用域与函数作用域”,ES6中添加了块级作用域,关键词let。
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let
    这波能反杀:@遥望双子座 嗯,v8引擎与其他引擎也有不同的地方
    42eccce13f4b:@波同学 知道啦。一点收获是:对语言机制的理解,需要看看对应编译器的实现。比如要深入理解js,需要看v8的实现。
    这波能反杀:ES6的知识在这系列里不决定涉及
  • c3805034bf08:波大神,太赞了!
    结合老师那个 test() 的例子,我觉得写成这样大家更好理解一点,不知道对不对
    for (var i=1; i<=5; i++) {
    function res(i){
    function timer(){
    console.log(i);
    }
    setTimeout( timer, i*1000 );
    }
    res(i)
    }
    请老师指正~:+1: :+1: :+1:
    309444e37215:@LHammer 你这个可以,但是没有用到闭包
    c3805034bf08: @波同学 期待更多的文章~ 👍👍👍
    这波能反杀:@LHammer 是对的,但是一般熟练了,都会使用函数自执行的方式
  • 77d555eb8d43:波同学,看完了你的文章我是知晓了闭包的意思,但是做你最后那个题并没有做出来,看到1楼答案结合我其他地方百度,setTimeout延时函数即有闭包特性对吧,每次执行结果是I值在增加,但是内部console.log(i)并没有立即执行,到了i=5的时候,是由谁执行的console?如果外层加上(function(i){})(i),这个是及时函数,相当于有了每次I增加都有执行环境,内部console所以执行了,不知道我理解的对不对
    这波能反杀:@白日梦想家zH 下一篇文章专门讲讲自执行,马上要写完了
    77d555eb8d43:@波同学 恩 还是有点不明白为什么要加(function(){})(),期待你更多的文章哈 学习了感觉很实用
    这波能反杀:@白日梦想家zH 你不用专研得那么深,关于setTimeout我上边也专门说过,他有内部自己的实现机制,通过回到函数的方式构成了一个闭包。
  • Oldboyyyy:for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
    console.log(i);
    }, i*1000 );
    }
    setTimeout 的可记住的并且可访问的作用于是window,所以不是闭包,回调函数执行结果是全局的i=6;
    for (var i=1; i<=5; i++) {
    (function (i) {setTimeout( function timer() {
    console.log(i);
    }, i*1000 )})(i);
    }
    setTimeout 的可记住的并且可访问的作用于是IIFE,符合定义前半段,回调函数执行环境是window,符合了在定义该函数作用域以外执行,所符合闭包,这时回调函数打印出来的i是IIFE的arguments里面的i
    不知道这样理解有没有问题
    不吃早餐我就是Mark:@过去式丶 为什么说timer函数是在外层的匿名函数作用域中声明呢?
    timer不是作为setTimeout的第一个参数吗?
    难不成发生了变量提升??
    Oldboyyyy:@波同学 恩恩,是的,谢谢指出。看过文章说setTimeout,是将待执行的函数压入指定时间以后的事件栈中,如果时间为零,就是当前事件栈结束以后,下一个事件栈顶部执行,这么理解setTimeout可以吗?
    这波能反杀:严格说来,timer函数是在外层的匿名函数作用域中声明,在setTimeout的内部机制中执行。
  • 34f9b47bef3d:javascript 是解释性语言,解释过程和编译过程不太一样
  • 12ab73476fdb:一口气读完这四篇文章,很不错,感觉对很多概念都清晰了许多
    这波能反杀:@frontX 好的
    12ab73476fdb:@波同学 嗯嗯,是的,感觉之前心太急,很多基础的知识反而落下了,博主加油更新,当作是帮我补补课了:stuck_out_tongue_closed_eyes:
    这波能反杀:@frontX 基础好了之后学习其他的就快速很多
  • 给我一炷香的时间:由于传递给`setTimeout`函数的回调函数是以匿名函数的形式在`setTimeout`的实际参数中定义的,那么匿名函数的作用域链为`[VO(self), VO(global)]`。并且`var`定义的变量没有块级作用域(不像`let`),实际上循环和如下定义一样:
    ```
    var i;
    for (i = 1; i <= 5; i++) {

    }
    ```
    那么有两种解决方法:
    - 使用`let`定义变量`i`,这样每一次的循环中都会创建一个新的作用域。
    - 在回调函数的外面加一层执行上下文,加了以后作用域链就是`[VO(self), VO(IIFE), VO(global)]`。由于`VO(IIFE)`在`VO(global)`前面,所以回调函数运行时访问的是`VO(IIFE)`中的变量`i`,而不再是`VO(global)`的变量`i`。
    ```
    for (var i=1; i<=5; i++) {
    setTimeout((function (i) {
    console.log(i);
    })(i), i*1000 );
    }
    ```
    如有理解错误,还望指出。
    给我一炷香的时间:@波同学 恩,我再去复习一遍。
    这波能反杀:粗略看了一下,你的理解有点问题。
    你的setTimeout的第一个参数,被你立即执行了,这样就没有构成一个闭包。与我文中闭包的定义不符。
    给我一炷香的时间:我觉得这个问题本质上是要创建一个作用域来保存每次循环的值,而`let`和闭包是两种可行的办法。
  • 4dd84fb715c1:感谢,javascript多少让人有些沮丧😣,闭包我总是处于理解与不理解的边缘,基础知识再理理,我想多看几遍总会彻底理解的
    4dd84fb715c1: @嬡莪莂趉 🙂嗯嗯,已入手一本,正在啃,加油加油!
    28b9797224a8:其实可以看看js高级程序设计的闭包那一块,有涉及模拟块级作用域、私有变量,多看几遍应该会好点;
    反正我也是在入门阶段,都是多看几遍;
    不过波波老师的文章已经完胜很多人了,哈哈
  • 744c2c46fe86: function timer(i) {
    console.log(i);
    }
    for (var i=1; i<=5; i++) {
    setTimeout( timer(i), i*1000 );
    }

    这样写貌似有些问题,不会一个一个的出现,而且最后还抛出一个23,不懂为什么
    744c2c46fe86:@波同学貌似有些懂了,谢了,很受教
    这波能反杀:你的timer(i)就直接把函数执行了,timer的定义与执行在同一个作用域了,所以根据定义这样不算是闭包。

    你要想办法通过闭包保存i值,可参考5楼写法
  • 不欺少年穷:有想法,有道理,点赞
  • 我才不是稻草人:真是好大一出戏,不知这篇"详细图解作用域链与闭包"又是为什么埋下了深深的伏笔:underage:
    这波能反杀::joy: this,原型,模块等都会涉及到,但是后面就不会常常提闭包了,因为以为已经潜移默化的深入骨髓:joy:
  • 枫祺玥:很棒的文章,我来倒个乱
    for (let i=1; i<=5; i++) {
    setTimeout( function timer() {
    console.log(i);
    }, i*1000 );
    }
    L_CINA:@soulwalker 你说的应该是setTimeout返回的一个id吧
    这波能反杀:@soulwalker 理解这个你先别用let
    e42aa8f44a7c:为什么会多输出一行
  • 朵朵鱼:不明觉厉+1😁😁👍👍
    7fbc1dd02a01:@744c2c46fe86 你的setTimeout的第一个参数应该是一个函数,你这样的话,是已经执行了,setTimeout的第一个参数就成了timer的返回值了
    744c2c46fe86:function timer(i) {
    console.log(i);
    }
    for (var i=1; i<=5; i++) {
    setTimeout( timer(i), i*1000 );
    }

    这样写貌似有些问题,不会一个一个的出现,而且最后还抛出一个23,不懂为什么
    这波能反杀::joy: 写的是一种叫做JavaScript的编程语言的一些知识,JavaScript是做网页需要用到的。
  • f3e3a1a21ce8:为什么console.log(timer)的值是1啊?
    哒哒_哒:这是因为timer被赋值的是一个计时器的Id,这个Id是数字类型的值。

本文标题:前端基础进阶(四):详细图解作用域链与闭包

本文链接:https://www.haomeiwen.com/subject/cehaittx.html