1. 宏任务和微任务
宏任务(macroTask)和微任务(microTask)都是异步中API的分类。
- 宏任务:setTimeout,setInterval,Ajax,DOM事件
- 微任务:Promise,async/await
微任务执行时机比宏任务要早。
console.log(100)
// 宏任务
setTimeout(()=>{
console.log(200)
})
// 微任务
Promise.resolve().then(()=>{
console.log(300)
})
console.log(400)
2. enevt loop和DOM渲染
JS是单线程的,而且和DOM渲染公用一个线程。JS执行的时候,需要留出时机用于DOM渲染。这个时机就是微任务执行后,宏任务执行前。
当Call Stack中的同步代码全部执行完毕之后,会进行DOM渲染,然后再触发event loop。
实例:当点击alert弹框的确认按钮后,才会渲染DOM元素,因为不点击确认按钮,Call Stack中的代码不会清空,就不会进行下一步:DOM渲染。
<body>
<div id="container"></div>
<script>
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length)
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染') // alert会阻断js执行,也会阻断DOM渲染
</script>
</body>
3. 宏任务和微任务的根本区别
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
<body>
<div id="container"></div>
<script>
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// 微任务:DOM渲染前触发
Promise.resolve().then(()=>{
console.log('lenght1', $('container').children().length)
alert('触发了微任务')
})
// 宏任务:DOM渲染后触发
setTimeout(()=>{
console.log('lenght2', $('container').children().length)
alert('触发了宏任务')
})
</script>
</body>
为什么微任务执行会更早?
可以从event loop去理解。假如程序执行的时候遇到宏任务,例如setTimeout
,此时会将setTimeout
中执行的代码放入Web APIs
中,等到所有的同步代码执行完,以及DOM元素
渲染完毕之后,执行event loop
,此时Web APIs
中的代码等到了时机,会移动到Callback Queue
中,event loop
将Callback Queue
中的代码移到Call Stack
中执行。
然而,当Call Stack
中遇到微任务时,例如Promise
,此时会将Promise
执行的代码放入micro task queue
。因为Promise
是ES6规定的,不是W3C规定的,因此执行时放入micro task queue
,micro task queue
的执行是先于DOM渲染的。微任务在DOM渲染前执行,宏任务在DOM渲染后执行,因此微任务的执行早于宏任务。
4. 实例
JS代码执行的顺序:
- 首先执行同步代码;
- 同步代码执行结束后,call stack被清空,开启envet loop;
- 执行微任务;
- 触发DOM元素渲染;
- 触发enevt loop;
- 执行宏任务。
async function async1(){
console.log('async1 start') // 顺序2
await async2()
console.log('async1 end') // 顺序6
}
async function async2(){
console.log('async2') // 顺序3
}
console.log('script start') // 顺序1
setTimeout(function(){
console.log('setTimeout') // 顺序8
}, 0)
async1()
// 初始化 promise 时,传入的函数会立刻被执行
new Promise(function(resolve){
console.log('promise1') // 顺序4
resolve()
}).then(function(){
console.log('promise2') // 顺序7
})
console.log('script end') // 顺序5