浏览器的多线程
JavaScript是脚本语言,它需要在一个宿主环境里才能运行,显然我们接触较多的宿主环境就是–浏览器!虽说JavaScript是单线程的,然而浏览器却不是!
浏览器的线程
GUI 线程
负责渲染浏览器界面,解析html,css,构建DOM树和RenderObject树,布局绘制等等
JS线程
也称之为js内核,负责处理JavaScript脚本程序,与GUI线程互斥
定时器线程
setInterval与setTimeOut所在的线程
事件触发线程
将满足出发条件的事件放入到任务队列
异步http请求线程
XHR在连接后通过浏览器新开一个线程请求
JavaScript引擎线程称为主线程,它负责解析JavaScript代码;其他可以称为辅助线程,这些辅助线程便是JavaScript实现异步的关键了!
function timeOut() {
setTimeout(() => {
console.log('timeOut');
}, 0)
}
function someTime() {
let s = Date.now();
while(true) {
if (Date.now() - s > 2000) {
console.log('some Time')
break;
}
}
}
console.log(1);
timeOut();
someTime();
console.log(3);
如果是以单线程那种解释来执行的话,这个打印顺序应该是:1 - time Out - some Time - 3 才对!然而,其真正的执行结果却是: 1 - some Time - 3 - time Out
主线程负责自上而下顺序执行,当遇到setTimeout函数后,便将其交给定时器线程去执行,自己继续执行下面的代码!从而达到异步的目的。
不仅如此,更关键的是:事件触发线程会将满足条件的事件放入任务队列中。
任务队列
当定时器线程计时执行完之后,会将回调函数放入任务队列中!
当这些任务加入到任务队列后并不会立即执行,而是处于等候状态!等主线程处理完了自己的事情后,才来执行任务队列中任务!
宏任务&微任务
然而,异步任务却又分为两种:一种叫“宏任务”(MacroTask 或者 Task),一种叫“微任务”(MicroTask)!
宏任务包括了:
- script
- setInterval
- setImmediate
- I/O
- UIrendering
微任务 - Promise
- Object.observe
- MutationObserver
- postMessage
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
这段代码的执行结果:1 - 4 - 3 - 2。LOOK!2是最后打印的,哪怕该计时器的时间设置为0。通过之前的同步和异步的解释,1和4先于2打印应该很好理解了,但同样是异步,3也优先于2打印,这又是为什么呢?答案就是因为 setTimeout属于宏任务,而Promise属于微任务!
浏览器的Event Loop
- 执行全局Script同步代码,形成一个执行栈;
- 在执行代码时当遇到如上异步任务时便会按上文所描述的将宏任务回调加入宏任务队列,微任务回调加入微任务队列;
- 然而,回调函数放入任务队列后也不是立即执行;会等待执行栈中的同步任务全部执行完清空了栈后引擎才能会去任务队列检查是否有任务,如果有那便会将这些任务加入执行栈,然后执行!
- 执行栈清空后,会先去检查微任务队列是否有任务,逐一将其任务加入执行栈中执行,期间如果又产生了微任务那继续将其加入到列队末尾,并在本周期内执行完,直到微任务队列的任务全部 清空,执行栈也清空后,再去检查宏任务队列是否有任务,取到队列队头的任务放入到执行栈中执行,其他可能又会产生微任务,那当本次执行栈中的任务结果清空后又会去检查微任务队列…
- 引擎会循环执行如上步骤,这就是Event Loop!
console.log('start');
setTimeout(() => {
console.log('time1');
Pormise.resolve().then(() => {
console.log('promise1');
})
}, 0);
setTimeout(() => {
console.log('time2');
Pormise.resolve().then(() => {
console.log('promise2');
})
}, 0);
Pormise.resolve().then(() => {
console.log('promise3');
});
console.log('end');
这段代码的打印顺序:
start - end - promise3 - timer1 - promise1 - timer2 - promise2