JS 中的异步任务分为宏任务 (macro task) 和微任务 (micro task) ,只有宏任务会进行事件循环。
JS 是单线程执行的,所有 JS 代码都要放在主线程中运行。 如果把异步 IO 等耗时较长的任务也放在主线程中处理,会阻塞后续同步代码的执行,造成卡顿等现象。因此,浏览器等运行环境额外设置了异步处理线程,专门用于处理异步事件。
事件循环描述了 JS 的运行机制,也就是同步和异步任务的执行过程。
循环过程:
整个 script 脚本将开启一次事件循环,而每个宏任务都将开启一次新的事件循环。
JS 可以操作 DOM 节点。如果 JS 是多线程的话,多个线程可以同时操作同一个 DOM 节点,比如一个在修改,另一个却要删除,这样太过混乱,导致浏览器很难处理。
虽然上面说到异步处理线程,但它和 JS 的执行无关。比如一个 ajax 请求,在发送请求时,浏览器将请求交给异步线程处理;请求完成后,异步线程将事件回调推入任务队列,等待 JS 主线程调用;请求的实现是由浏览器 IO 线程和服务器完成的。
宏任务包括:
微任务包括:
宏任务,依赖浏览器等宿主环境; 微任务,在 JS 引擎中执行,不会造成阻塞,也不参与事件循环。
JS 在执行一段代码的时候,除了会把同步任务放入执行栈,还会把微任务放到执行栈后面,形成一个微任务队列( JS 中可访问 queueMicroTask)。 在执行栈中的同步任务执行完成后,JS 会先调用微任务队列中的任务,然后再去调用宏任务队列。
因此,在同一次循环中,微任务比宏任务优先执行;在整个执行过程中,微任务复用一个队列,而宏任务共用一个队列。
在同一次循环中,微任务比宏任务优先执行,任务按照推入队列的顺序执行(FIFO)。
在处理微任务和宏任务互相包含的情况,记住两点:
下面几道题:
h 表示宏任务,w 表示微任务,求解运行顺序。
let results = []
function microTask (name, callback) { // 微任务
new Promise((resolve) => {
resolve()
}).then(() => {
results.push(name)
callback && callback()
})
}
function macroTask (name, callback) { // 宏任务
setTimeout(() => {
results.push(name)
callback && callback()
}, 0)
}
function tick1 () { // 结构
macroTask('h1')
macroTask('h2', () => {
microTask('w1')
microTask('w2')
})
microTask('w3')
}
tick1()
setTimeout(() => {
console.log(results.join(' '))
}, 1000)
结果:
> w3 h1 h2 w1 w2
let results = []
function microTask (name, callback) {
new Promise((resolve) => {
resolve()
}).then(() => {
results.push(name)
callback && callback()
})
}
function macroTask (name, callback) {
setTimeout(() => {
results.push(name)
callback && callback()
}, 0)
}
function tick2 () {
microTask('w1', () => {
microTask('w2', () => {
microTask('w3')
})
})
microTask('w4')
}
tick2()
setTimeout(() => {
console.log(results.join(' '))
}, 1000)
结果:
> w1 w4 w2 w3
let results = []
function microTask (name, callback) {
new Promise((resolve) => {
resolve()
}).then(() => {
results.push(name)
callback && callback()
})
}
function macroTask (name, callback) {
setTimeout(() => {
results.push(name)
callback && callback()
}, 0)
}
function tick3 () {
microTask('w1', () => {
macroTask('h1', () => {
microTask('w2')
})
})
microTask('w3', () => {
microTask('w4')
})
}
tick3()
setTimeout(() => {
console.log(results.join(' '))
}, 1000)
结果:
> w1 w3 w4 h1 w2