咱们先看一张图 大概了解下
script标签里的代码相当于一个宏任务,放入到调用栈中执行,执行console.log(‘global begin’) ;发现 setTimeout宏任务,会把setTimeout函数放入Macro Queue宏任务里面执行,在setTimeout里面的掉代码继续放入调用栈,调用栈发现微任务queueMicrotask,会把queueMicrotask函数放入Micro Queue微任务里面执行,当微任务执行完之后,会开启渲染线程,执行渲染操作
看一下 下面代码 讲述执行流程
// 事件循环, 主线程
while (macroQueue.waitForMessage()) {
// 1. 执行完调用栈上当前的宏任务(同步任务)
// call stack 调用栈
// 2. 遍历微任务队列,把微任务队里上的所有任务都执行完毕(清空微任务队列)
// 微任务又可以往微任务队列中添加微任务
for (let i = 0; i < microQueue.length; i++) {
// 获取并执行下一个微任务(先进先出)
microQueue[i].processNextMessage()
}
// 3. 渲染(渲染线程)
// 4. 从宏任务队列中取 一个 任务,进入下一个消息循环
macroQueue.processNextMessage();
}
产生宏任务的方式
- script 中的代码块
- setTimeout()
- setInterval()
- setImmediate() (非标准,IE 和 Node.js 中支持)
- 注册事件
产生微任务的方式
- Promise MutationObserver
- MutationObserver
- queueMicrotask
下是利用事件循环的经典示例
//批量操作
let messageQueue = [] // 全局
let sendMessage = message => {
messageQueue.push(message)
debugger
if (messageQueue.length === 1) {
queueMicrotask(() => {
const json = JSON.stringify(messageQueue);
messageQueue.length = 0; // 清空消息数组
console.log(messageQueue)
// ajax("url-of-receiver", json);
console.log(json)
});
}
};
//queueMicrotask(() => {
//console.log('queueMicrotask')
//})
sendMessage('刘备')
sendMessage('关羽')
sendMessage('曹操')
sendMessage('曹操')
sendMessage('曹操')
微任务只创建了一次,创建之后输出语句一直挂起没有执行,是因为调用栈里面还有代码没跑完,主线程后面还有内容往 messageQueue 里 push。等调用栈的代码跑完了调用栈为空,事件循环才开始把微任务里的三行代码压入调用栈。
所以看上去是一次性把所有消息输出了。这段代码是为了理解事件循环专门这样设计的,没必要深究 messageQueue 的长度为什么判断 1 和清空,打断点看看程序执行步骤就可以了,重点是理解为啥微任务里的代码后执行了。
执行本轮其他任务那里,把所有的消息都 push 了执行回调那里的时候,才在真正使用 messageQueue 变量
() => {
const json = JSON.stringify(messageQueue);
ajax()
}
反正看到回调函数,就在脑子里把它们放到队列里挂起