You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
varoutput=function(i){setTimeout(function(){console.log(newDate,i);},1000);};functiontest(){for(vari=0;i<5;i){output(i);// 这里传过去的 i 值被复制,而不是引用}console.log(newDate,i);}
写于:2017-06-08
起
最近一道面试试题非常火热,堪称面试界网红:
不理解闭包,变量作用域和setTimeout函数的同学很多会给出答案A:0,1,2,3,4,5和答案B:5,0,1,2,3,4;不奇怪,但正确答案却是5,5,5,5,5,我以前也是。当然相比较,说出答案B至少比答案A多知道setTimeout函数的用法,重点不在那个延迟1000ms,重点在setTimeout函数,后面会细说。
首先三个概念:
等明白上面第一个settimeout概念后,最后一行为什么先打印最后一行的结果了;
明白变量作用域后,就会明白console.log('end',new Date(),i)中的i是for循环声明的那个i变量,因为var声明的变量不存在代码块({})作用域的概念,所以最后打印的值是5;
明白函数后,和变量作用域一起理解,我们可以得出类似如下所示的图(如果理解不正确,还请大神指正)
在for循环声明的五个TimeOut Callback函数都有对变量i的引用,而不是拷贝。因为5个timeout函数都涉及到延迟执行的情况,所以当主线程执行完后(end被打印时),timeout这些回调依次执行(队列:FIFO),此时i的值已经为5了,知道以上这些,后面就简单多了。
解
开始回到正题:
其实写出这个函数期望输出5,0,1,2,3,4,要达到这个结果,方法有多种,这里列出典型的三种:
方法2:函数调用按值传递:
方法2:函数调用按值传递技巧版(利用setTimeout第三个参数):
方法3: ES6 使用le指令声明:
细度上面的三种方法,其实他们相似度很高。首先方法1(声明即执行)和方法2(提前声明,调用时执行),其实他们的思路完全一致,都利用了JavaSrcipt中函数基本类型变量传值,都是值的拷贝,而不是值的引用,然后通过在for循环中执行一个闭包函数,建立一个闭包作用域,来保证引用的i值为注册该回调函数时的值。立即即执行,如果看着别扭,下面这样写也是可以的:
然后方法3,是利用ES6 let命令声明变量块级作用域的概念,和前面for循环使用var声明i不同的是,var声明的i在整个test()函数作用域内有效,每一次循环, 新的i值都会覆盖旧值;而let声明的, 当前的i只在本轮循环有效, 在ES6称为块级作用域。所以每一次循环的i其实都是一个新的变量,所以也导致打印end时,报i不存在,未声明的错误,这就是块级作用域的效果,所以5个timeout回调函数虽然都引用了变量i,但实际上这5个i是独立的,仅在自己的块级作用域内有效,其写法类似于:
最后
所以总体来看,上面的方法解决的思路都是从作用域这个概念上下手的,前两者利用function声明形成了自己的作用域,后者利用let命令形成的块级作用域,而来确保对i值的正确引用。
以上就是自己对这个网红面试题的深入理解,如果有说的有错或模棱两可的地方,还请不吝指教。
The text was updated successfully, but these errors were encountered: