1. **在深入了解事件循环之前,我们不妨先做道题**
思考下面代码的执行结果
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
resolve(3)
}).then(val => {
console.log(val)
})
console.log(4)
输出结果是:
2
4
3
1
你做对了吗?这里我们不禁要问:
* 为什么 `setTimeout()` 设定的时间是 0 毫秒,但 1 却是在最后输出的?
* 为什么 `Promise.then()` 的回调函数会在 4 输出之后执行,而不是在 2 之后?
其实,在 JavaScript 中,代码的执行顺序并不是完全按照它们的书写顺序,而是取决于它们在 **事件循环** 中的顺序。
2. **什么是事件循环?**
事件循环,即 Event Loops。用于协调事件、用户交互、JavaScript 脚本、DOM 渲染、网络请求等等的执行顺序问题。
一个遵循 ECMAScript 标准的代理(浏览器或 JS 引擎)也必须遵循事件循环机制。
事件循环是由一个或以上的 **任务队列** 组成的。
3. **什么是任务队列?**
任务队列,即 Task Queues,是一组任务的集合(Sets)。
由于 JavaScript 是 **单线程** 语言,所以在 JS 中所有的任务都需要排队执行,这些任务共同组成了 **任务队列**
,依次排队执行的过程,形成一个 **执行栈(Execution Context Stack)** 。
在任务队列中最先执行是同步任务。
4. **什么是同步任务?**
同步任务,即 Synchronous Task。就是当上一个任务执行完成后,接下来可以立即执行的任务。它们在主线程上依次排队执行,直到清空。
比如,下面代码中的 `for()` 和 `console.log()` 将会依次执行,最终输出 `0 1 2 done`。
for (let i = 0; i < 3; i++) {
console.log(i)
}
console.log('done')
与同步任务相比,异步任务的执行充满了不确定性。
5. **什么是异步任务?**
异步任务,即 Asynchronous
Task。就是需要等待被通知才以执行的任务。也就是说,它们不会直接进入主线程执行,而是进入到微任务队列或下一次事件循环中的任务队列进行等待。
常见的异步任务有:
* `XMLHttpRequest()`
* `Promise.then()`、`Promise.catch()`、`Promise.finally()`
* `setTimeout()`、`setInterval()`
等待,就意味着不确定性。比如,`XMLHttpRequest()` 等待服务器响应,`Promise.then()` 等待
`resolve()`,`setTimeout()` 等待时间。
所以虽然都是异步任务,它们的执行的顺序仍然会有所区别。因此,我们将它们分为 **宏任务** 和 **微任务** 。
6. **什么是宏任务?**
宏任务,即 MacroTask。就是指进入任务队列的任务。比如:
* `setTimeout()`、`setInterval()`
* `document.appendChild()`
* [`postMessage()`](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FWindow%2FpostMessage)
* [`MessageChannel()`](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FMessageChannel)
* [`setImmediate()`](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FWindow%2FsetImmediate)(Node.js 环境)
* `<script />`
由于当前任务队列已经处于执行状态,所以任务队列中遇到的宏任务将进入到下一次事件循环的任务队列,而微任务则会被放入到本次事件循环的微任务队列中。
7. **什么是微任务?**
微任务,即 Microtask 或 Jobs。每次事件循环都会有一个初始为空的微任务队列。常见的微任务有:
* `Promise.then()`、`Promise.catch()`、`Promise.finally()`
* [`MutationObserver()`](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FMutationObserver)(浏览器环境)
* `process.nextTick()`(Node.js 环境)
8. 测试题
看到这里,JavaScript 的事件循环机制差不多就解释完了,涉及到了同步任务、异步任务、宏任务和微任务以及它们之间的关系。
下面有几道测试题,同学们可以测试一下自己的理解程度:
* 简单
setTimeout(() => {
console.log(1)
}, 0)
for (let i = 2; i <= 3; i++) {
console.log(i)
}
console.log(4)
setTimeout(() => {
console.log(5)
}, 0)
for (let i = 6; i <= 7; i++) {
console.log(i)
}
console.log(8)
* 普通
console.log(1)
async function async1() {
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
async1()
setTimeout(() => {
console.log(4)
}, 0)
new Promise(resolve => {
console.log(5)
resolve()
})
.then(() => {
console.log(6)
})
.then(() => {
console.log(7)
})
console.log(8)
* 困难
console.log(1)
function a() {
return new Promise(resolve => {
console.log(2)
setTimeout(() => {
resolve()
console.log(3)
}, 0)
})
}
a().then(() => {
console.log(4)
})
* * *
参考资料:
* [ECMAScript® 2022 Language Specification](https://links.jianshu.com/go?to=https%3A%2F%2Ftc39.es%2Fecma262%2F%23sec-agents)
* [HTML Standard](https://links.jianshu.com/go?to=https%3A%2F%2Fhtml.spec.whatwg.org%2Fmultipage%2Fwebappapis.html%23event-loops)
* [并发模型与事件循环 - JavaScript | MDN](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FEventLoop)
* [window.setTimeout - Web API 接口参考 | MDN](https://links.jianshu.com/go?to=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FWindowOrWorkerGlobalScope%2FsetTimeout)
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。