概念
JavaScript语言是单线程的,这意味着同一时间只能执行一个任务
浏览器是多线程的,一个浏览器通常由以下常驻线程组成:
- GUI 渲染线程
- JavaScript引擎线程
- 定时触发器线程
- 事件触发线程
- 异步http请求线程
GUI 渲染线程 与 JavaScript引擎线程互斥,JS引擎执行任务时永远不会进行渲染(render),仅在任务完成后才会绘制对 DOM 的更改。
如果一项任务执行花费的时间过长,浏览器将无法执行其他任务,无法处理用户事件,因此,在一定时间后浏览器会在整个页面抛出一个如“页面未响应”之类的警报,建议你终止这个任务。这种情况常发生在有大量复杂的计算或导致死循环的程序错误
异步任务
为了不让某些耗时较长的任务造成阻塞,使浏览器进入假死状态,产生了异步任务
所有任务可以分为同步任务与异步任务两种,当执行同步任务遇到一个异步任务时,就在event table
(事件表)中注册回调函数,同步任务继续执行。期间异步任务完成时,回调函数会被放入event queue
(事件队列)
异步任务不是JavaScript引擎线程处理,比如计数器由浏览器的定时触发器线程计数;XMLHttpRequest在连接后通过浏览器新开一个线程请求,检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理
宏任务与微任务
宿主环境(浏览器、node)提供的方法是宏任务(macrotask),也叫tasks
- script
- setTimeout
- setInterval
- requestAnimationFrame
- I/O
- UI rendering
语言标准(js引擎)提供的是微任务(microtask),也叫jobs
- 原生Promise
- Object.observe(已废弃)
- MutationObserver
浏览器的事件循环过程
- 从宏任务队列中取出script代码(最早的任务),将同步任务放入执行栈中,执行栈中的代码产生的异步任务会被加入到任务队列
- 当执行栈中的代码执行完,即栈为空时,将先从微任务队列(microtask queue)中取出一个任务,放入执行栈中执行
- 当微任务队列中的任务都执行完成后,从宏任务队列(microtask queue)中取出一个任务,放入执行栈中执行,执行完后重复2
- 如果宏任务队列为空,则休眠直到出现宏任务
执行栈清空后总是会先执行完微任务队列中的任务,再从宏任务队列取出一个任务
例子
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(function () {
console.log('promise1');
})
.then(function () {
console.log('promise2');
});
console.log('script end');
上面代码的输出顺序为: script start
, script end
, promise1
, promise2
, setTimeout
setTimeout为宏任务,Promise为微任务被加入到各自的任务队列中,输出script end
后,执行栈为空,先去微任务队列中执行Promise,执行完后再去执行宏任务队列中的setTimeout。
链接
https://imweb.io/topic/58e3bfa845e5c13468f567d5
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!