浅谈JS宏任务&微任务&Event Loop
先来看段代码
// 示例代码1
console.log("start", 1)
// 宏任务
setTimeout(() => {
setTimeout(() => {
console.log("setTimeout", 12)
}, 0)
Promise.resolve().then(() => {
console.log("Promise", 8)
})
}, 0)
Promise.resolve().then(() => {
console.log("Promise,", 3)
Promise.resolve().then(() => {
console.log("Promise", 5)
Promise.resolve().then(() => {
console.log("Promise", 7)
})
})
setTimeout(() => {
console.log("setTimeout", 10)
}, 0)
})
Promise.resolve().then(() => {
console.log("Promise", 4)
Promise.resolve().then(() => {
console.log("Promise", 6)
})
setTimeout(() => {
console.log("setTimeout", 11)
}, 0)
})
setTimeout(() => {
console.log("setInerval", 9)
}, 0);
console.log("start2", 2)
// 执行结果
// start 1
// start2 2
// Promise 3
// Promise 4
// Promise 5
// Promise 6
// Promise 7
// Promise 8
// setInerval 9
// setTimeout 10
// setTimeout 11
// setTimeout 12
- 为什么会出现这样的执行顺序呢?
因为JS执行机制就是这样的 我也莫得办法。。。
- JS执行机制
提到js执行机制那就必须得提几个概念、宏任务、微任务、异步任务、同步任务,以及事件循环,在了解这些概念之前 先看一张示例图
- 先说一下什么是宏任务、微任务、同步异步任务
宏任务:
- 整体的script代码
- setTimeout
- setInterval
- setImmediate
- I/O (比如Ajax操作从网络读取数据)
- UI render
微任务:
- process.nextTick
- Promise
- Async/Await(实际就是promise)
- MutationObserver(html5新特性)
同步任务:
示例代码1 console.log("start", 1) 就是一个同步任务
异步任务:
setTimeout、setInterval、 I/O、Promise、Async/Await等等
示例代码1 中setTimeout promis.then 都属于异步任务。且setTimeout 属于宏任务 promise.then 属于微任务
事件循环
上述示例图中 这部分 宏任务循环执行就是事件循环 宏任务=> 微任务 => 宏任务
看到这里如果还不懂 没关系,我们来借助示例代码1 来理解
- 首先 我们应该先明确一个执行概念,在同步代码>微任务队列>宏任务队列,在同步代码执行完毕之后微任务优先级高于宏任务 然后我们在区分哪些代码是同步 哪些代码是异步、在示例一代码中 除console 之外 其余的都是异步代码,所以可以明确两个同步代码的执行顺序
console.log("start", 1)
console.log("start2", 2)
- 明确了所有同步代码的执行顺序之后,我们在区分 哪些任务是微任务 与宏任务
- 微任务
// promise.then 属于微任务
Promise.resolve().then(() => {
console.log("Promise,", 3)
setTimeout(() => {
console.log("setTimeout", 8)
}, 0)
})
// promise.then 属于微任务
Promise.resolve().then(() => {
console.log("Promise", 4)
Promise.resolve().then(() => {
console.log("Promise", 5)
})
setTimeout(() => {
console.log("setTimeout", 9)
}, 0)
})
- 宏任务
// setTimeout属于宏任务
setTimeout(() => {
setTimeout(() => {
console.log("setTimeout", 10)
}, 0)
Promise.resolve().then(() => {
console.log("Promise", 6)
})
}, 0)
// setTimeout属于宏任务
setTimeout(() => {
console.log("setInerval", 7)
}, 0);
- 综上所述 当同步任务执行完毕之后 此时的任务队列应该是这样的
微任务:[
Promise.resolve().then(() => {
console.log("Promise,", 3)
setTimeout(() => {
console.log("setTimeout", 8)
}, 0)
}),
Promise.resolve().then(() => {
console.log("Promise", 4)
Promise.resolve().then(() => {
console.log("Promise", 5)
})
setTimeout(() => {
console.log("setTimeout", 9)
}, 0)
}), ]
宏任务:[
setTimeout(() => {
setTimeout(() => {
console.log("setTimeout", 10)
}, 0)
Promise.resolve().then(() => {
console.log("Promise", 6)
})
}, 0),
setTimeout(() => {
console.log("setInerval", 7)
}, 0);
]
当同步任务执行完毕之后,接下来会执行微任务队列,微任务队列执行完毕之后,才会进入事件循环状态,执行宏任务
- PS:当执行微任务时,微任务内含有宏任务,那么会将宏任务push到宏任务队列中,如果微任务中含有微任务将进行同步执行,代码如下
// 当微任务中含有宏任务
Promise.resolve().then(() => {
console.log("Promise,", 3)
// 此时遇到setTimeout不会立刻执行,setTimeout为宏任务,会push到宏任务中按序执行
setTimeout(() => {
console.log("setTimeout", 8)
}, 0)
})
// 当微任务中含有微任务
Promise.resolve().then(() => {
console.log("Promise", 4)
// 此刻遇到promise.then为微任务,立刻执行此微任务
Promise.resolve().then(() => {
console.log("Promise", 5)
})
// 此时遇到setTimeout不会立刻执行,setTimeout为宏任务,会push到宏任务中按序执行
setTimeout(() => {
console.log("setTimeout", 9)
}, 0)
}),
- PS:当执行宏任务时,宏任务内含有宏任务,那么会将宏任务push到宏任务队列中,如果宏任务中含有微任务将进行同步执行,代码如下
setTimeout(() => {
// 遇到宏任务 push到宏任务队列中
setTimeout(() => {
console.log("setTimeout", 10)
}, 0)
// 遇到微任务,继续执行
Promise.resolve().then(() => {
console.log("Promise", 6)
})
}, 0),
如果看到这还没能理解代码示例1中的执行顺序 也没关系,我们用js代码实现一下整个js执行机制
// javascriptCode 必做所有js代码
// wTask 我们比做微任务队列
// hTask 我们比做宏任务队列
// Javascript 用来执行我们的js代码
// codeList 值: key-代码类型 code-执行代码 child-宏任务或者微任务里的子任务
// 将示例代码转化为得到javascriptCode如下
let javascriptCode = [{
key: 0,
code: "start1"
}, {
key: 2,
code: "",
child: [
{
key: 2,
code: "setTimeout:12",
},
{
key: 1,
code: "Promise:8",
}
]
},
{
key: 1,
code: "Promise:3",
child: [
{
key: 1,
code: "Promise:5",
child: [
{
key: 1,
code: "Promise:7",
},
]
},
{
key: 2,
code: "setTimeout:10",
}
]
},
{
key: 1,
code: "Promise:4",
child: [
{
key: 1,
code: "Promise:6",
},
{
key: 2,
code: "setTimeout:11",
}
]
},
{
key: 2,
code: "setInerval:9",
},
{
key: 0,
code: "start2:2",
},]
- 接下来我们用一个javascript类 执行我们的javascriptCode
// 首先要明确 我们的执行顺序是 同步代码=>微任务=>进入事件循环=>宏任务=>微任务=> 事件循环
class Javascript {
constructor(codeList) {
this.wTask = [] // 微任务队列
this.hTask = [] // 宏任务队列
this.codeList = codeList || [] // js代码
this.init() // 初始化
}
init() {
// 先执行同步任务 然后将异步任务放入对应队列
this.codeList.forEach(item => {
if (item.key === 0 && item.code) {
// 同步任务
console.log("同步任务直接执行", item.code)
}
if (item.key === 1) {
// 微任务
this.wTask.push(item)
}
if (item.key === 2) {
// 宏任务
this.hTask.push(item)
}
})
// 同步任务执行完毕之后再执行微任务
this.implementTask(1)
}
// 当宏任务变化时 触发任务执行机制
implementTask(type) {
// type 决定宏任务还是微任务
const list = type === 1 ? this.wTask : this.hTask
if (list?.length > 0) {
const currentTask = list[0]
// 先执行同步任务
if (currentTask.code) {
console.log("微任务里的同步任务", currentTask.code)
}
// 如果包含子任务
if (currentTask.child) {
currentTask.child.forEach(item => {
if (item.key === 1) {
this.wTask.push(item)
}
if (item.key === 2) {
this.hTask.push(item)
}
})
}
if (type === 1) {
this.wTask.shift()
} else {
this.hTask.shift()
}
if (this.wTask.length > 0) {
this.implementTask(1)
} else {
this.implementTask(2)
}
}
}
转载自:https://juejin.cn/post/7098324584155316232