在看别人的代码时发现setTimeout(func,0)的写法很不解。为什么要settimeout 0?
在网上查资料都引入了单线程的JS这个话题。
一点点来看。
有这样一段代码
alert(1);
setTimeout("alert(2)", 0);
alert(3);
按照正常的理解,延迟0秒就是不延迟执行嘛,但其实不是。输出的结果是1,3,2。
为什么会这样呢?
settimout函数造成一种多线程异步的假象,让你认为js可以顺序执行主逻辑的代码,并同时开另外一个线程去处理要延迟的代码。
但其实JS引擎是单线程,浏览器的一个页面只有一个线程在处理。
那么JS为什么会给人多线程的假象呢?
因为JS的运行是事件驱动的。
浏览器中很多行为都是异步的,鼠标点击,窗口拖拽等。如果响应这些事件呢?浏览器把这些异步的事件都放在放入一个行为队列中。
JS引擎单线程的,顺序处理队列中任务。
settimeout的延迟做某件事情并没有另开一个线程处理,而是在设定的延时时间到了之后在队列中新建了一个任务。
那么上面的代码就很好理解了。
alert(1)输出1之后,延迟0秒,队列中新建一个任务。
现在队列中有两个任务,一个是执行alert(3),一个是执行alert(2)。
由于js现在正处于alert(3)的任务中,所以会先执行完这个任务,再取任务队列中settimeout插入的alert(2)的任务。
setTimeout的执行时间点只是加入js主执行队列中的时间点,至于什么时候执行,是由js引擎线程按顺序执行的队列来决定。所以很多时候用setTimeout做动画不流畅的原因。
为什么要settimeout 0 呢?
setTimeout(func, 0)神奇在哪儿?那就是告诉js引擎,在0ms以后把func放到主事件队列中,等待当前的代码执行完毕再执行。
这里的关键就是改变了代码流程,把func的执行放到了等待当前的代码执行完毕再执行。
- 让浏览器渲染当前的变化(很多浏览器UI render和js执行是放在一个线程中,线程阻塞会导致界面无法更新渲染)
- 重新评估”script is running too long”警告
又引出一个问题
既然js多线程异步是假象,那么Ajax请求到底是不是异步呢?
继续查资料,查到了浏览器的一些工作机制。
浏览器是多线程的,每开一个页面都至少开有下面几个线程:
- javascript引擎线程
- UI界面渲染线程
- 浏览器事件触发线程
- Http请求线程
其中Http请求线程是执行完了就终止的线程,比如我们的Ajax请求。只要有一个ajax请求,浏览器就会开一个http请求线程去异步地执行。
当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的事件处理队列中等待处理。
所以JavaScript引擎始终是单线程运行回调函数。
参考资料: