说一道面试题,不要栽跟头

原创 老姚 随笔 我也来说说系列 18085阅读 2016-06-15 22:02:32 举报

题如下:

结果是:0 1 2 3 3 3

很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。
为了防止初学者栽在此问题上,此文稍微分析一下。
都考察了那些知识点呢?
异步、作用域、闭包,你没听错,是闭包。
我们来简化此题:

先打印2,后打印1。
因为是 setTimeout 是异步的。
正确的理解 setTimeout 的方式:
有两个参数,第一个参数是函数,第二参数是时间值。
调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。
没啥不好理解的。就像我们给按钮绑定事件一样:

这么写完,会弹出1吗。不会!!只是绑定事件而已!
必须等我们去触发事件,比如去点击这个按钮,才会弹出1。
setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。
setTimeout的时间值是怎么回事呢?
比如:

setTimeout(fn, 2000)

我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用 fn。如果前面还有其他的事件,那就等待。
因此 setTimeout 是一个约会从来都不准时的童鞋。
继续看:

程序会不会报错?
不会!而且还会准确得打印1。
为什么?
因为真正去执行 console.log(i) 这句代码时,var i = 1 已经执行完毕了!
所以我们进行 dom 操作。可以先绑定事件,然后再去写其他逻辑。

这么写,完全是可以的。因为异步!

es5中是没有块级作用域的

也就说i可以在for循环体外访问到。所以是没有块级作用域。
但此问题在es6里终结了,因为es6,发明了let。

这回我们再来看看原题。
原题使用了 for 循环。循环的本质是干嘛的?
是为了方便我们程序员,少写重复代码。
让我们倒退50年,原题等价于:

因为 setTimeout 是注册事件。根据前面的讨论,可以都放在后面。
原题又等价于如下的写法:

这回你明白了为啥结果是0 1 2 3 3 3了吧。

那个,老姚你说它是闭包,又是怎么回事?
为了很好的说明白这个事情,我们把它放到一个函数中:

上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:

点击每个div都会弹出3。道理是一样的。因为 alert(i) 中的 ifn 作用越中的,因而这是闭包。
《javascript忍者秘籍》书里把一个函数能调用全局变量,也称闭包。
因为作者认为全局环境也可以想象成一个大的顶级函数。
怎么保证能弹出 0,1,2 呢。
解决之道:以毒攻毒!
再创建个闭包!!

或者如下的写法:

因此原题如果也想 setTimeout 也弹出 0,1,2 的话,改成如下:

本文完。

评论 ( 55 )
最新评论

在执行setTimeout之前的时候for语句i的值已经从0循环到4了,等到了setTimeout的时候i已经是5了

老姚 53F 2018-04-17 10:47:28 54F

for循环的运算过程就是如此。链接标题

人马在冬至追风 2018-04-16 14:35:12 53F

姚神,我不理解为什么会i++了三次

何遇 48F 2018-01-24 10:47:04 52F

setTimeout是一次执行函数,这里是i1s后执行,仅仅执行一次;for(var i=0;i<5;i++),i的每次取值都是执行setTimeout这个函数,并没有执行setTimeout里面的function(即闭包函数),setTimeout里面的function是有setTimeout的定时触动的,也就是i1s后执行,也就是说i从0~5时,一共执行了5次的setTimeout()函数,此时的i的值是5,由于for语句的执行速度远小于1s,所以,1s后,由setTimeout()函数定时触动的闭包函数function()开始执行,alert(i);i的值已经是5了,所以相继打印5次5.

老姚 50F 2018-01-24 10:00:45 51F

setTimeout(fn, time),首先这是个函数setTimeout执行,他是同步执行的。表示的意思是告诉浏览器,time时间后,要执行fn,只是“告诉”而已。
time时间后,浏览器把fn插入事件队列中,如果插入时,事件队列时空的,那么fn就立即执行了。因为fn在setTimeout执行完毕之后才执行的,因此是异步的,即异步回调。
而setTimeout(fn, 1000 i)可以写成
var j = 1000
i;
setTimeout(fn, j)
因此是同步执行的。
fn中的i,是闭包中的i,始终拿到i最新的保存值的,而轮到fn执行时,i已经累加成了5了。

何遇 48F 2018-01-23 17:45:41 50F

好像不对。我再看看

何遇 48F 2018-01-23 16:54:10 49F

就是这个理解的。你打印一下看看效果就是5s执行一次5

Fathands 2017-02-28 21:07:09 48F

原谅我是菜鸟新手
有一道类似的题一直有疑问想请教一下
javascript 代码

这道题是不是就变成了:
javascript 代码

结果是先输出5,然后每隔一秒输出5
但是我不理解这里的时间间隔 1000 i 是怎么处理的,不应该是10005,每隔五秒运行一次吗?求指教

老姚 46F 2017-02-16 13:55:07 47F

找的是i,但真正调用函数(闭包)时,i已经变成3了。

夏天不做梦 2017-02-16 11:58:38 46F

请问 for循环 当条件 i<3 时 ,里面循环的最后一个 i 结果已经是2+1=3了, 但是这个3不会去执行下一步操作 只是作为一个内部循环结果数值一直存在着 ,可以这样理解吗 ,盼回复 非常感谢!

老姚 44F 2016-08-05 08:46:15 45F

谢谢指出啊。挺认真的,此文读了基本几遍都没发现。原文加上onclick了。最后写不写console.log都行,明白原理就可以了。

老姚 43F 2016-08-05 08:40:24 44F

原文那块忘了写onclick

zhangcf 2016-08-04 23:36:43 43F

老姚,你这样写不能弹出啊 return function() {alert(i);};要在后面加个括号。。。return function() {alert(i);}();然后还有。最后一个代码段。要console.log()123。最后外面那个console.log(i);要删掉。不然会出现012012。。。

老姚 41F 2016-08-04 18:03:05 42F

谢谢。

zhangcf 2016-08-04 17:57:43 41F

新人还是不太懂闭包,慢慢学!我再研究研究这题!!感谢老姚!!!

诚如神之说 39F 2016-06-21 23:49:01 40F

谢老姚的耐心讲解,辛苦了。

老姚 38F 2016-06-21 23:12:05 39F

nice,看来你是理解透彻了。还得补充一点。匿名函数自执行不是闭包(这点很重要,很多人都把二者混淆了)。里面那个返回的那个函数才是闭包。其引用的变量是外层匿名函数里的i。匿名函数里面的变量i是外部传进来的。比如传2,那么此i便一直是2.不会再变,因为没有其他的代码去改动这个i了。如果像原题本身那样,那个i是不断变化的。拿到的结果肯定最后的值了。

诚如神之说 2016-06-21 23:00:22 38F

讲的很详细,看例子看懂了。
闭包函数中引入了外部函数的变量,如果闭包函数没有立即执行,则最终执行时会以引入的外部函数变量值的最终值为自己运行时的值(就如第一个例子所示,闭包任务会排列在最后)。
以毒攻毒,再引入闭包,其实就是相当于让闭包函数当下立马执行,这样内部的变量就被赋予了确定值。
所以最后的例子是这样的结果
div[0].onclick=function(){alert(0)};
div[1].onclick=function(){alert(1)};
div[2].onclick=function(){alert(2)};

蚂蚱1105 2016-06-21 16:06:38 37F

很详细很赞 lz创建个自己的群吧

老姚 35F 2016-06-21 12:46:50 36F

setTimeout注册的是匿名函数自执行返回的那个函数。下一步需要去理解闭包了。