本文来自 @xiaoyuze88 链接:http://xiaoyuze88.github.io/
太久没碰代码了,那天想到关于循环调用setTimeout
实现每隔一秒输出递增的数的那个问题,搞了搞,发现很多概念模糊了,在此总结下。
所谓的循环调用setTimeout
实现递增输出,就是说用for
循环10次,每隔一秒输出一个从0~9的数。
不多说,直接上最终代码再说,细节后面再谈。
for (var i = 0; i < 10; i++) {
//这里用闭包,为每一个i生成一个独立的上下文环境,传递给里面的console.log,而不会受到setTimeout延时而影响
(function (i) {
`setTimeout`(function () {
console.log(i);
}, 1000 * i)
})(i);
}
这里主要的问题在:
- 闭包的概念
- 也是最终要的,关于
setTimeout
等函数的工作机制。
首先闭包,这里就不多说了,在这里,闭包的作用就是给闭包内的函数生成一个不受外面环境干扰的上下文环境,由于js的作用域问题。
如果这里不用闭包,写成诸如:
for (var i = 0; i < 10; i++) {
`setTimeout`(function () {
console.log(i);
}, i * 1000);
}
会发现,每隔一秒钟,输出一个10。
这是由于这一个for循环的执行,瞬间就完成了,也就是说,瞬间注册了10个延时执行的函数,每一个隔一秒钟执行。
当注册的时间点到来,开始执行setTimeout
中的语句,由于定义域的问题,此时console.log(i)
的这个i指向的是已经到达10的for循环中的i,这就是为什么要用闭包来给setTimeout
设置独立的上下文环境,而避免需要访问i时访问到了外面的变量。
另外,如果细想一下,会发现setTimeout
的工作过程多少让人有点迷惑,到底setTimeout
等延时类函数在浏览器中是如何运作的?这就牵扯到下一个问题,关于浏览器中是如果运作的问题。
浏览器中,JS引擎是单线程的,假设一个浏览器中有三个常驻线程,既JS引擎线程、渲染线程、事件触发线程,还有处理完即结束的线程如AJAX异步请求。
其中,JS线程与渲染线程是互斥的,这是为了避免JS控制DOM时与页面渲染发生冲突。而对于JS线程,它是由事件驱动的,由于单线程,所有任务依队列排序。如果页面上触发了事件,如onclick=function(){}
、或者由setTimeout
添加了一个函数、ajax
请求返回的事件等,所有新添加的任务位于队尾等待处理。
由于是单线程,如果线程被阻塞,如while(true){}
死循环,则一切新添加的任务都将被阻塞。
由上面所述,就可以理解为什么setTimeout
或setInterval设置的延时事件并不是真是函数处理的延时时间,既setTimout(code,1000)并不是一定会在1秒后处理,这段代码发生的仅仅是在1秒后,将待处理函数排与js任务队列末尾。