-
在深入了解事件循环之前,我们不妨先做道题
思考下面代码的执行结果
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 中,代码的执行顺序并不是完全按照它们的书写顺序,而是取决于它们在事件循环中的顺序。
-
-
什么是事件循环?
事件循环,即 Event Loops。用于协调事件、用户交互、JavaScript 脚本、DOM 渲染、网络请求等等的执行顺序问题。
一个遵循 ECMAScript 标准的代理(浏览器或 JS 引擎)也必须遵循事件循环机制。
事件循环是由一个或以上的任务队列组成的。
-
什么是任务队列?
任务队列,即 Task Queues,是一组任务的集合(Sets)。
由于 JavaScript 是 单线程 语言,所以在 JS 中所有的任务都需要排队执行,这些任务共同组成了任务队列,依次排队执行的过程,形成一个执行栈(Execution Context Stack)。
在任务队列中最先执行是同步任务。
-
什么是同步任务?
同步任务,即 Synchronous Task。就是当上一个任务执行完成后,接下来可以立即执行的任务。它们在主线程上依次排队执行,直到清空。
比如,下面代码中的
for()
和console.log()
将会依次执行,最终输出0 1 2 done
。for (let i = 0; i < 3; i++) { console.log(i) } console.log('done')
与同步任务相比,异步任务的执行充满了不确定性。
-
什么是异步任务?
异步任务,即 Asynchronous Task。就是需要等待被通知才以执行的任务。也就是说,它们不会直接进入主线程执行,而是进入到微任务队列或下一次事件循环中的任务队列进行等待。
常见的异步任务有:
-
XMLHttpRequest()
-
Promise.then()
、Promise.catch()
、Promise.finally()
-
setTimeout()
、setInterval()
等待,就意味着不确定性。比如,
XMLHttpRequest()
等待服务器响应,Promise.then()
等待resolve()
,setTimeout()
等待时间。所以虽然都是异步任务,它们的执行的顺序仍然会有所区别。因此,我们将它们分为宏任务和微任务。
-
-
什么是宏任务?
宏任务,即 MacroTask。就是指进入任务队列的任务。比如:
-
setTimeout()
、setInterval()
-
document.appendChild()
-
setImmediate()
(Node.js 环境) -
<script />
由于当前任务队列已经处于执行状态,所以任务队列中遇到的宏任务将进入到下一次事件循环的任务队列,而微任务则会被放入到本次事件循环的微任务队列中。
-
-
什么是微任务?
微任务,即 Microtask 或 Jobs。每次事件循环都会有一个初始为空的微任务队列。常见的微任务有:
-
Promise.then()
、Promise.catch()
、Promise.finally()
-
MutationObserver()
(浏览器环境) -
process.nextTick()
(Node.js 环境)
-
-
测试题
看到这里,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) })
-
参考资料:
网友评论