1 for(var i = 0; i < 5; i++) { 2 setTimeout(function() { 3 console.log(i); 4 }, 2000) 5 }
这段代码大家都不陌生吧,相信很多久经面试的小伙伴一定不陌生。可是是不是还有很多人不理解其中的原因,现在我就带大家一起详细的分析一下。
先让我们看一下最基本写法:
1 for(var i = 0; i < 5; i++) { 2 console.log(i); 3 }
这个毫无疑问应该是0,1,2,3,4。可以为啥加一个函数就不行了呢?当初我也迷糊了好久。好了,不买关子了,让我们来看一下最开始的那个函数,一步一步来分析。首先当i = 0 时,执行了一个setTimeout这样一个异步函数,里面输出一个i,应该是0没错吧,可是别忘了函数不是立即执行的,而是要等一秒之后才进行的。然而这时循环并不会停止,继续循环。i = 1;又调用了一个异步函数,里面输出一个i,此时应该是1。可是i对于函数是共享的,所以之前那个本应该输出0的异步函数中i在这时被替换成了1。现在是不是就有两个输出1的异步函数了。2,3,4类似,因为每一步前面的i值都会被替换,最后都换成了最后一个值。应该是5个4。可是当循环i=4时还要+1,i成了5虽然循环没有进行了,但是i最后被赋值成了5,所以所有的i就成了5了。输出了5个5,呼,终于真相大白了。
现在让我们研究一下,如何才能按照正常的值输出1,2,3,4呢。解决方法就是让每一步的i对于函数都是单独的不会因为共享而被替换。
方案1:添加一个立即执行函数将i立刻传到函数中成为函数的参数,因为后面的i值得变化并不会改变之前函数的参数值
1 for(var i = 0; i < 5; i++) { 2 (function(j) { 3 setTimeout(function() { 4 console.log(j); 5 }, 1000) 6 })(i) 7 }
方案2:利用ES6特性let,因为let时块级作用域,所以并不会影响之前复制的i的值
1 for(let i = 0; i < 5; i++) { 2 setTimeout(function() { 3 console.log(i); 4 }, 1000) 5 }
方案3:其实setTimeout这个函数还有第三个参数甚至更多,从第三个参数起,后面的参数为传入函数中的参数,因为原理与第一种方法类似
1 for(var i = 0; i < 5; i++) { 2 setTimeout(function(j){ 3 console.log(j); 4 }, 1000, i) 5 }
其实解决方案应该远不止这么多,但是原理都是一样的,所以就不一一列举了,喜欢研究的小伙伴可以分享一下其它方法……
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/16606.html