Appearance
事件循环
javascript 从诞生之日起就是一门单线程的非阻塞的脚本语言。
如果 javascript 是多线程的,那么当两个线程同时对 dom 进行一项操作,例如一个向其添加事件,而另一个删除了这个 dom,此时该如何处理呢?
当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。
概念
- 执行栈(Execution Context Stack):JS 引擎通过执行栈来管理函数调用,每个函数调用都会进入执行栈并创建一个执行上下文。
- 任务队列(Task Queue):所有的任务都会被放入相应的任务队列中,分为宏任务(Macrotask)和微任务(Microtask)两类。
- 微任务(Microtask):包括 process.nextTick, Promise.then/catch/finally, MutationObserver 等任务。
- 宏任务(Macrotask):包括 script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering 等任务。
机制
- js 引擎遇到同步代码进入主线程执行,遇到异步代码并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务;
- 当一个异步事件返回结果后,js 会将这个事件加入队列中;
- 主线程内任务执行完毕,会去队列中读取对应函数,进入主线程执行;
- 上述过程会不断重复,也就是常说的事件循环,其中异步代码分为宏任务和微任务,优先执行微任务;
案例
提示
new Promise 中的代码是同步执行的,then 中代码才是异步的。
await 后面紧跟的代码会立即执行,并暂停当前函数的执行,将后续的语句作为微任务添加到微任务队列中。
异步回调函数执行顺序
js
console.log('start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
Promise.resolve().then(function () {
console.log('Promise')
})
console.log('end')
查看答案
start
end
Promise
setTimeout
首先输出 start,然后异步回调函数 setTimeout 和微任务 Promise 被添加到它们各自的队列中。
接着输出 end,此时所有同步代码执行完毕,
主线程开始处理微任务队列中的任务,输出 Promise。然后再处理宏任务队列,输出 setTimeout。
多层嵌套的异步函数执行顺序
js
console.log('start')
setTimeout(function () {
console.log('setTimeout1')
setTimeout(function () {
console.log('setTimeout2')
}, 0)
Promise.resolve().then(function () {
console.log('Promise2')
})
}, 0)
Promise.resolve().then(function () {
console.log('Promise1')
})
console.log('end')
查看答案
start
end
Promise1
setTimeout1
Promise2
setTimeout2
首先输出 start,
然后异步回调函数 setTimeout1 和微任务 Promise1 被添加到它们各自的队列中。
接着输出 end,此时所有同步代码执行完毕,
主线程开始处理微任务队列中的任务,输出 Promise1。
然后再处理宏任务队列中的 setTimeout1,继续添加异步回调函数 setTimeout2 和微任务 Promise2 到它们各自的队列中。
接着处理微任务队列中的任务,输出 Promise2,然后处理宏任务队列中的 setTimeout2,输出 setTimeout2。
js
async function asyncFunc() {
console.log('async start')
await console.log('Sync run')
await new Promise(res => {
console.log('promise run')
res()
}).then(() => console.log('then run'))
console.log('async end')
}
console.log('start')
asyncFunc()
console.log('end')
查看答案
start
async start
Sync run
end
promise run
then run
async end
首先输出 start,
然后调用异步函数 asyncFunc,输出 async start。在执行 await console.log('Sync run') 输出 Sync run 后,会将后面的部分作为微任务添加到微任务队列中,然后暂停 asyncFunc 函数的执行,
主线程开始执行同步代码接着输出 end,
此时微任务队列中有一个任务,执行该任务,输出 promise run,then run 添加到微任务队列,
继续执行微任务输出 then run,
最后输出 async end 。