美文网首页前端码无界前端前端学习
图例详解那道setTimeout与循环闭包的经典面试题

图例详解那道setTimeout与循环闭包的经典面试题

作者: 这波能反杀 | 来源:发表于2017-02-28 00:51 被阅读16526次

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

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

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

    公众号二维码

    ar_indus

    相关文章

      网友评论

      • 冷光啊啊啊:波老师,什么时候可以看啊。。。
        这波能反杀:@冷光啊啊啊 我马上弄
      • 7e1494e2eff1:用立即执行函数就可以解决了
      • 7661739843c6:为什么是恢复中:fearful:
      • Lazysunshi_ff25:博主你好,很喜欢你写的 前端进阶系列,其中有一篇https://www.jianshu.com/p/9b4a54a98660
        是恢复中状态,请问怎么才能访问呢
      • 尘埃落定_Y:利用最后提到的 私有化,就解决了。 把time 函数拿出来 在 异步里面 调 5次 每次传入 i ,i 在time 的作用域里面被私有化了。 每次执行流 改变了 i ,也不会被影响。
        尘埃落定_Y:给自己点赞的人 都很帅
      • 记录生活记录bug:for (var i=1; i<=5; i++) {
        setTimeout( function timer() {
        console.log(i);
        }, i*1000 );
        }直接讲var给成let就解决了
      • a2d7b36d630e:所以关键还是因为在 for循环中就 创建了5个独立的闭包函数, 每循环一次就创建了一个闭包函数, 每次循环的闭包函数其实都是独立的。
      • a2d7b36d630e:创建了5个闭包, 每次传给闭包的值不一样, i的每次变化并不影响上次闭包 value的值。
      • a2d7b36d630e:i虽然变化了, 但是value其实跟i没什么关系, 在for循环中value就确定了了值, 并且保存了起来;
      • a2d7b36d630e:for (var i=1; i<=5; i++) {

        (function(j) {
        var value = i;
        setTimeout( function timer() {
        console.log(value);
        }, i*1000 );
        })(i)
        }
      • a2d7b36d630e:好像看懂了, 没有闭包前, 所有console.log引用的是全局变量 i, 而i在console.log执行前的值就被改变了; 引入自执行函数闭包后, 只是把i的值传给闭包, 分别创建了5个闭包
      • 路某人_lu:想问大佬两个问题:1中间那个长代码快的输出第一行为什么是undefined而不是20;2.最后一个代码块,不是说setTimeout内的函数会在for循环之后执行吗,那为什么匿名函数绑定的i值不i是6,而是每个i.谢谢啦,小白问题多。。
      • mmmage:大神,fn.toString = function(){ return 30}这一行是啥意思?为什么执行后下边console.log(fn)就变成30了:disappointed_relieved:
      • af4093f71a34:大神,恕我无知
        fn.toString = function(){
        return 30;
        };
        .toString是啥意思?事件吗?把函数变成字符串?
      • f242b658719a:fn.toString = function() {
        return 30;
        }
        这一句会对代码有什么影响不懂,求大神解释一下啊
        这波能反杀:@有梦想的咸鱼_5207 我记得某一篇文章里有详细解释
      • eabae0900027:当i无限大的时候不会爆栈吗?队列里会有n个settimeout,所以建议用async,await或者co配合generator,或者递归尾调用
      • b7bfb8ee19a4:波波老师:这句话不太懂[将i值保存在一个闭包中,当setTimeout中定义的操作执行时,则访问对应闭包保存的i值即可。]
        for循环里定义了5个setTimeout的闭包。而当这些操作开始执行时,for循环的i值难道不是已经先一步变成了6了么。所以将i值保存在闭包里面的不也是6么
        for (var i=1; i<=5; i++) {
        setTimeout( (function(i) {
        return function() {
        console.log(i);
        }
        })(i), i*1000 );
        }
        这波能反杀:@Wrigley 创建了5个闭包,一个闭包保存一个值
      • 小太阳_9ae2:setTimeou的内容t等待到函数调用栈清空之后才开始执行是因为它是异步的吗?
      • 公鸡下啊:直接console.log(i)不就完事了吗
      • 5567c7396dc4:看了一下您的文章 又去看了下高程 一些闭包的应用比如在for (var i=1; i<=5; i++) { alert(i)}中访问i每次都是5 按照高程理解了一下是不是因为js在ES5中没有块级作用域所以要模仿出一个类似块级作用域 所以要在外面定义一个自执行的匿名函数。
        这波能反杀:@Iaminjinan 闭包的理解,要从内存管理那里开始,不然总会有点想不明白的。模仿块级作用域是闭包的一个应用。而不是理解闭包要结合块级作用域来。
        5567c7396dc4:@波同学
      • Sunnyww:保存在内存的i值不会被修改掉吗,i=1,i=2,i=3.....
      • 7cd363b098c9:请问 `队列:先进先出` 这张图片使用什么软件做的?? O(∩_∩)O谢谢
        Promise__: @Willard 应该是progress。。楼主的前端进阶前几篇里的评论里有
      • Gingbery:写的很棒,但是有一点不太理解,例子中的延迟时间为i*1000,但是观察到的延迟时间都是1s,请问是为什么?
        Promise__: @Gingbery 因为打印第一个值是1秒后,打印第二个值是2秒后,打印第三个值是3秒后,相对于前一个值,每个打印出来的值都间隔为1秒
      • 一缕殇流化隐半边冰霜:setTimeout 最后给的那个例子的答案好像不对,那个b变量会提升,所以会输出20,文章中给的答案是输出了undefined
        这波能反杀:@一缕殇流化隐半边冰霜 那我就不知道你说的是哪个例子了 ~ 这篇文章只有一个关于b的例子,输出是没错的
        一缕殇流化隐半边冰霜:@波同学 我把你的代码直接粘到Chrome的console里面打印出来的。。。b的20是可以打印出来的
        这波能反杀:我给出的答案应该是没错的,要不你写个demo试一下?
      • 罗彬727:for( var i=0;i<=5;i++){
        setTimeout(clock(i),i*1000);
        function clock(i){
        console.log(i)
        }
        } ,这样也可以
      • 12cb6232f15c:有一个疑问:
        “ 而这个队列执行的时间,需要等待到函数调用栈清空之后才开始执行。”
        函数调用栈应该很没有算清空吧,不是还有栈底的全局上下文吗?
        Promise__: @12cb6232f15c http://ghmagical.com/article/page/id/H61NOVU0RZ9Y建议看这篇文章
      • de96d6727049:写的非常好,评论里有用的地方也很多,不过我有个地方的认识和你不同:“我们就必须借助闭包的特性,每次循环时,将i值保存在一个闭包中”,我认为是再利用IIFE运行会产生块级作用域(类似let)的方式保存i,并不是闭包的特性,这个题目中用到闭包,完全是因为如果只是立即执行函数,返回的不是一个函数的话,不会按照时间间隔输出,而是立即执行,然后输出,比如我把你的方法二中的闭包部分删掉:
        for (var i=1; i<=5; i++) {
        setTimeout( (function(i) {
        //return function() {
        console.log(i);
        //}
        })(i), i*1000 );
        }
        代码的i值还是可以访问到(说明访问到i的正确值不是闭包的关系),但是不能隔一秒输出一次,而是所有的console被立即执行了。
        所以我认为您上述给出的两种解决方法都很好,但是闭包和IIFE在其中起到的作用我不是很认同。如果我的认识有不对的地方,欢迎纠正我的认识。:blush:
        de96d6727049:@波同学 在你这篇文章的评论里我看到有人使用settimeout的第三个参数的方法,然后用你的方法在chorme中调试发现,不同于使用闭包的情况,使用闭包时scope中依次是local-closure-global,执行时i的值放在closure中;而使用第三参数传入i时,scope中依次是local-global,i的值存放在local中,没有使用闭包,很好奇是怎么实现的,查找了半天没有找到,大神知道么?
        de96d6727049:@波同学 多谢你的提醒,我下来做了几个实验,发现确实是我之前的理解一直有矛盾的地方,确实是保存在闭包中。立即执行函数只是将i调用时的值传入闭包保存,形式上完全可以不用立即执行函数的方式,直接函数调用。核心点还是闭包。
        这波能反杀:@该用户还没想好叫什么 你理解肯定是有一些问题的。你对闭包的了解应该有点不到位,建议再思考一下:i值是如何保存。想想你的例子里为什么i值没有保存,以及你的例子里输出的12345到底是什么,与保存在闭包里的i值有什么区别
      • evanoxu:波老师,能解释下这个变种的原理吗?
        for (var i = 1; i <= 5; i++) {
        setTimeout((function(i) {
        console.log(i);
        })(i), i * 1000);
        }
        这波能反杀:@evanoxu 你对闭包了解太浅了,另外几篇文章去看看
        evanoxu:@波同学 我也看不懂原理。如果按照执行,是没有setTimeout的作用,直接循环输出的。求分析…
        这波能反杀:@evanoxu 这是什么变种?
      • 6536e0a782f4:火狐、360、谷歌浏览器的执行结果怎么还不一样呢?
        火狐:
        undefined
        function fn()
        function fn()
        setTImeout 10ms.
        setTimeout 20ms.


        360:
        undefined
        fn() {
        setTimeout(function() {
        console.log('setTImeout 10ms.');
        }, 10);
        }
        function 30
        10
        setTImeout 10ms.
        setTimeout 20ms.

        谷歌:
        undefined
        function 30
        function 30
        10
        setTImeout 10ms.
        setTimeout 20ms.
        这波能反杀:@Amy_Angela 内部实现不一样
        不用去纠结具体的优先级
      • 6536e0a782f4:setTimeout(function() {
        console.log(a);
        }, 0);

        var a = 10;

        console.log(b);
        console.log(fn);

        var b = 20;

        function fn() {
        setTimeout(function() {
        console.log('setTImeout 10ms.');
        }, 10);
        }

        fn.toString = function() {
        return 30;
        }

        console.log(fn);

        setTimeout(function() {
        console.log('setTimeout 20ms.');
        }, 20);

        fn();


        请问这个例子中
        fn.toString = function() {
        return 30;
        }
        fn.toString在执行上下文创建的时候会被创建吗?还是说function变量 fn创建了.toString这个function变量也被创建了?另外setTimeout这个函数也会被创建吗?这个函数创建后保存这个函数的引用的变量名称是什么呢?
      • 10e5b44da6a6:你好,请教下
        function foo(){
        for(var i = 0 ; i<2 ; i++){
        setTimeout(function timer() {
        console.log(i) ;

        },i*1000);
        }
        }
        这个代码中,最后打印2 2,这个我能理解,我想问一下这里的 i*1000,我的理解是1*1000,2*1000.也就是说第一个2是间隔一秒钟之后打印的,第二个2是间隔二秒之后打印的。但是我看浏览器打印的间隔时间是一样的,这是怎么回事?请指教。谢谢
        useless1:@踏遍万里河山 怎么打印不出2了执行了可以打印出2啊
        10e5b44da6a6:@踏遍万里河山 大概懂了,谢谢,现在才看到
        踏遍万里河山:@词不达意_e6f5 首先,你的这段代码打印不出来2
        然后,出现时间间隔一样的原因是前一个的1s也包含在后一个的时间里。
      • bestvow:我会用let吧:smiley:
      • 一wei渡江:console.log(fn);
        function fn() {}
        fn.toString = function() {
        return 30;
        }
        console.log(fn);
        前面的fn为函数体, 后面的fn为30
        这里不太理解
        函数的toString属性和valueOf属性有什么关系
        同时重写fn. toString和fn. valueOf, 他们都会被调用, toString先调用valueOf后调用, 结果为valueOf的值.只重写toString, 这个函数会被调用两次. 只重写valueOf只会被调用一次.
        它们是fn. toString=fn. valueOf=function(){return 30}这样吗,指向同一个函数toString先调用valueOf后调用.
        fn直接回车输出内容的过程是什么
        一wei渡江:@波同学 谢谢,看了你的文章收获很大
        这波能反杀:@一wei渡江 我也不知道
      • 夏目祐太:function fn(i) {
        setTimeout(function() {
        console.log(i);
        }, 1000)
        }

        for(var i = 0; i < 5; i++) {
        fn(i);
        }

        突然想到也可以这么解决,简单粗暴
      • 6b5a2e2b487a: var name = "The Window";
          var object = {
            name : "My Object",
            getNameFunc : function(){
        alert(this.name);
              return function(){
                return this.name;
              };

            }
          };
        alert(object.getNameFunc()());
        useless1:@EvanChenug 调用过程中确定this,最后一次相当于直接window下调用啊
        6b5a2e2b487a:为什么
        6b5a2e2b487a:波老师,return this.name会指向window哇
      • Dream4ever:前几天在项目中刚好遇到了这个问题,百思不得其解,今天真是走运,看到了这篇文章,疑虑顿消,大谢!
      • Admin_4f18:你好,我运行出来的是第一个为 10 , function fn(),funtion 30 这个就不懂了,10,settimeout 10ms, settimeout 20ms;
        demo11:@老三不爱吃面 给个例子,我试试看
        demo11:@老三不爱吃面 怎么看出的console.log会隐式调用toString方法呢?
        useless1:@Admin_4f18 因为console.log会隐式调用toString方法,前面重新定义了toString方法啊:relieved:
      • darayo::joy: 好像懂了~
      • bbbbbbbbbb_3076:点个攒 , settimeout:clap:
      • CatCCsCircus:w3school的级别:
        for(var i=0;i<5;i++){
        setTimeout("console.log("+i+")",i*1000);
        }
        hkcmd:没看懂?
      • 239f01adc9eb:支持支持1!!
      • 始悔不悟:谢谢,很有帮助
        这波能反杀:@始悔不悟 有帮助就好
      • 析子:虽然我正在讲高中课本里的算法语句,可是我还是看不懂这么高深的学问:disappointed_relieved:,佩服你,加油哦,㊗️你早日签约:blush:
      • 柒淡墨:很棒
      • 风萧萧梦潇:另外几种方式:
        1.利用setTimeout第三个参数
        for (var i=1; i<=5; i++) {
        setTimeout( function timer(i) {
        console.log(i);
        }, i*1000,i );
        }
        2.利用bind方法
        for (var i=1; i<=5; i++) {
        setTimeout( function timer(i) {
        console.log(i);
        }.bind(null,i), i*1000 );
        }
        3.利用let
        for (let i=1; i<=5; i++) {
        setTimeout( function timer() {
        console.log(i);
        }, i*1000 );
        }
        279bae7e32be:bind(null)是什么意思啊?不太理解 求解惑
        0c09725b07dc: @风萧萧梦潇 👏👍
        微醺岁月:for (var i = 0; i < 5; i++) {
        setTimeout(console.log.bind(console,i), 1000 * i)
        }
      • 744bb97e6c1a:大神忘记了es6的let:smile:
        尤小小:@波同学 😁
      • 56e26a4f693b:感谢。看到这熟悉的例子真亲切,之前看书的时候看着看着就晕了,经LZ这么一解释现在虽然还是有点迷糊但好歹有点理解了:dizzy_face:
      • 朵朵鱼:佩服,把操作变成一篇文章肯定要花好多时间👍👍
        这波能反杀:嗯,确实花时间 - -
      • WangChloe:波同学,请问setTimeout传入第三个参数i,同时里面的方法接收i出现的结果是什么原理,一直没找到这个问题的答案,是在一次笔试中看到的题目
        WangChloe:@风萧萧梦潇 好的,谢谢,我去看看
        风萧萧梦潇:第三个参数是传给匿名函数的参数。这篇文章写的挺好http://blog.csdn.net/fightingboy8888/article/details/54311156
      • JohnsonChe:写的很好
      • Forgetthose:for (var i=1; i<=5; i++) {
        (function(b){
        return setTimeout( function timer() {
        console.log(b);
        }, b*1000 )
        })(i)
        }
      • 无戒:大神。厉害
      • 淘淘笙悦:写的很详细,实在感谢

      本文标题:图例详解那道setTimeout与循环闭包的经典面试题

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