单线程的JavaScript

在看别人的代码时发现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引擎始终是单线程运行回调函数。

浏览器线程机制

参考资料:

1.Javascript是单线程的深入分析
2.Javascript可否多线程