JavaScript语言是单线程的,同步任务在主线程上执行,形成一个执行栈(JS stack)。
异步任务,包括setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI rendering等,也被称作宏任务(macrotask)。
主线程之外存在一个先进先出的任务队列(Task queue),异步任务的运行结果会在任务队列中放置对应事件(callback)。
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列的事件并执行之。主线程从任务队列中读取事件的过程是不断重复循环的,这种运行机制便称为事件循环Event Loop。
JavaScript中还存在另外的一类异步任务,包括process.nextTick、Promise、MutationObserver,以及已经废弃的Object.observe,被称做微任务(microtask),它们的回调函数会被放进微任务队列(Microtask queue)。
执行栈中的当前任务执行结束后,会立即检查并处理微任务队列,处理过程中产生的微任务也会被立即处理掉,而不用等下次事件循环。
通俗来讲,macrotask的规则是等下一班车,microtask的规则是挂在当前车尾,而且允许现做现卖。
下面是JavaScript事件循环Event Loop执行示例:
Task queue
|
Run script
setTimeout callback
|
Microtask queue
|
Promise then
Promise then
|
JS stack
|
|
Log
|
script start
script end
promise1
promise2
setTimeout
|
具体执行过程大致如下:
- JS线程启动,创建事件循环
- 当前同步代码Run script为任务队列的第一项
- script加入执行栈
- 执行
console.log('script start')
,输出日志“script start” - 执行
setTimeout...
,其回调函数被插入任务队列 - 执行
Promise.resolve()...
,其回调函数console.log('promise1')
被插入微任务队列 - 执行
console.log('script end')
,输出日志“script end” - script出栈,执行栈为空,立即检查并处理微任务队列
- 微任务
console.log('promise1')
入执行栈执行,输出日志“promise1” - 处理过程中产生新的微任务
console.log('promise2')
立即入执行栈处理,输出日志“promise2” - Run script事件结束,开始检查任务队列
console.log('setTimeout')
入执行栈执行,输出日志“setTimeout”- 进行新一轮事件循环
所以这段代码Log日志的出现顺序为:script start、script end、promise1、promise2、setTimeout。
参考:
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.ayqy.net/blog/javascript-macrotask-vs-microtask/