首先咱们应该先知道浏览器内核渲染进程是由多线程组成的,其中主要包括如下几个javascript
一、GUI渲染线程java
。主要负责渲染浏览器界面,解析HTML和CSS,构建DOM树和RenderObject树,布局和绘制等浏览器
。当页面须要重绘或者因为某种操做引起页面回流时,该线程就会执行多线程
。注意,GUI渲染线程和JS引擎线程是互斥的,当JS引擎线程运行的时候,GUI渲染线程就会被挂起,GUI更新会被保存在一个队列中,等待JS引擎空闲下来当即执行异步
二、JS引擎线程函数
。又称为JS内核,主要负责处理javascript脚本程序布局
。JS引擎负责解析javascript脚本,运行代码spa
。JS引擎一直等待着任务队列中任务的到来,而后加以处理,一个Tab页面中不管何时都只有一个js线程在执行js程序线程
。一样,JS引擎线程和GUI渲染线程是互斥的,因此若是JS线程执行的时间过长,这样会形成页面的渲染不连贯,致使页面渲染加载阻塞队列
三、事件触发线程
。该线程归属于浏览器而不是JS引擎线程,用来控制事件循环。(能够这样理解,JS引擎本身都忙不过来,须要浏览器另开线程协助)
。当JS引擎执行代码块 如setTimeOut(也可来自浏览器内核的其余线程,如鼠标点击,AJAX异步请求等)时,会把对应的任务添加到事件触发线程中
。当对应的事件符合触发条件被触发时,该线程会把该事件添加到待处理的任务队列的队尾,等待JS引擎处理
。因为JS引擎是单线程的,因此这些待处理任务队列中的事件都得排队等待JS引擎处理(JS引擎空闲时才会去执行)
四、定时触发器线程
。传说中的setTimeOut和setInterval所在的线程
。浏览器定时计数器并非由javascript引擎计数的(由于JS引擎是单线程的,若是线程处于阻塞状态就会影响计时的准确性)
。所以经过单独的线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
。须要注意,W3C在HTML标准中规定,setTimeOut小于4ms的时间间隔算为4ms
五、异步http请求线程
。在XMLHttpRequest链接后是经过浏览器新开一个线程请求
。在检测到状态变动时,若是设置有回调函数,异步线程就产生状态变动事件,将这个回调再放入事件队列中,由js引擎空闲时执行
咱们都知道JS引擎是单线程的,这是由于JS的做用主要是与用户互动,以及操做DOM,这决定了他只能是单线程的,不然会带来不少同步的问题,假如JS有两个线程,同一时间一个线程再某个DOM节点上添加东西,一个线程再删除该DOM节点,这时候就会出现问题
而后咱们还须要理解一些概念:
.JS分为同步任务和异步任务
1.同步任务都在主线程上执行,造成一个执行栈
2.主线程以外,事件触发线程管理着一个任务队列,只要异步任务有告终果,就会在任务队列中添加一个事件
3.一旦执行栈中全部同步任务执行完毕,此时,JS引擎空闲,系统就会读取任务队列,将可执行的异步任务添加到可执行栈中,开始执行
如此循环上面的步骤
看到这里咱们大概明白了,为何setTimeOut推入的事件没有在规定的时间执行,这是由于,当它推入到事件队列中时,主线程尚未空闲,JS引擎还在执行主线程的任务,因此天然会有偏差
关于定时器:
上述事件循环机制的核心是:JS引擎线程和事件触发线程
但事件上还有一些隐藏的细节,譬如,调用setTimeOut后,是如何等待特定时间后才添加到事件队列中的?
是JS引擎检测的么?固然不是了。它是由定时器线程控制
为何要单独的定时器线程?由于JS引擎是单线程的,若是处于阻塞线程状态就会影响计时的准确,所以颇有必要单独开一个线程来计时
何时会用到定时器线程?当使用setTimeOut或setInterval时,他须要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。
譬如:
setTimeout(function(){
console.log('hello!');
},1000);
这段代码的做用是1000毫秒计时完成后(由定时器线程计时),将回调函数推入事件队列中,等待主线程执行
setTimeout(function(){
console.log('hello!');
},0);
console.log('begin');
这段代码的效果是最快的时间内将回调函数推入事件队列中,等待主线程执行
注意
执行结果是先begin后hello!
虽然代码的本意是0毫秒后就推入事件队列,可是W3C在HTML标准中规定,要求setTimeout中低于4ms的时间间隔算为4ms
就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(由于只有可执行栈内空了后才会主动读取事件队列)