前言
JavaScript作为一种单线程、非阻塞的语言,其执行模型是通过事件循环机制来实现的。所以理解JavaScript的运行原理,事件循环机制是一个关键的概念。
V8引擎作为JavaScript的主要运行时环境之一,其事件循环机制的实现细节也值得我们深入探讨。
正文
在了解事件循环机制之前,我们得补充一点前置知识
我们先来了解什么是同步代码和异步代码?
微任务和宏任务又是什么呢?
1. 同步代码和异步代码:
- 同步代码: 同步代码是按照代码的先后顺序依次执行的,一个任务必须等待前一个任务完成后才能执行。
- 异步代码: 异步代码允许一个任务在执行的同时,其他任务也可以继续执行。当一个异步任务完成时,系统会通知,从而不会阻塞整个程序的执行。
2. 微任务和宏任务:
- 微任务: 微任务是一种特殊的异步任务,它在当前代码块执行完成后立即执行,优先级比宏任务高。常见的微任务包括 Promise 的回调函数、MutationObserver 等。
- 宏任务: 宏任务是常规的异步任务,它在当前代码块执行完成后执行,优先级比微任务低。常见的宏任务包括 setTimeout、setInterval、setImmedate()、I/O操作等。
既然我们已经知道了这些知识了
那我们开始讲解事件循环机制(Event Loop)的执行流程了
事件循环机制(Event Loop)的执行流程分为五步
- 执行同步代码(属于宏任务)
- 同步执行完毕后,检查是否有异步需要执行
- 如果有,则执行微任务队列中的所有任务
- 微任务队列执行完毕后,如果有需要就会渲染页面
- 执行异步宏任务,也是开启下一次时间循环
接下来我们通过代码去分析这五步流程
let a = 1;
console.log(a); // 1
setTimeout(() => {
console.log(++a);
}, 1000);
console.log(a); // 1
- 首先声明并赋值变量
a = 1
。 - 紧接着打印出
a
的值1
,其中像这些赋值操作都是同步代码。 - 然后遇到
setTimeout
函数,这是一个异步操作。V8 引擎会将这个异步任务放入宏任务队列中,等待适当的时机再执行。同时,主线程会继续执行下面的同步代码。 - 接下来打印出
a
的值1
。这时a
的值还没有被修改。 - 1 秒钟后,
setTimeout
的回调函数被执行。此时a
的值为1
,打印出1
,然后a
自增为2
。
难度提升咯
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
setTimeout(() => {
console.log(5);
});
console.log(6); // 12645
- 首先打印出
1
。 - 接下来创建一个新的 Promise 对象,在 Promise 构造函数中立即打印出
2
。 - Promise 的构造函数中
resolve()
被调用,这个 Promise 对象的状态变为 resolved。 - 然后
.then()
方法被调用,会将其中的回调函数作为微任务加入到微任务队列中。 - 紧接着又调用了一次
.then()
,同样会将其回调函数加入微任务队列。 - 之后遇到了
setTimeout
,它会将回调函数加入到宏任务队列中。 - 最后打印出
6
。
难度再升级
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
setTimeout(() => {
console.log(4);
}, 0);
});
setTimeout(() => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 0);
}, 0);
console.log(7); // 1273546
- 首先打印出
1
。 - 接下来创建一个新的 Promise 对象,在 Promise 构造函数中立即打印出
2
。 - Promise 的
resolve()
被调用,这个 Promise 对象的状态变为 resolved。 - 然后
.then()
方法被调用,会将其中的回调函数作为微任务加入到微任务队列中。 - 在
.then()
回调函数中,首先打印出3
。 - 然后遇到了
setTimeout
,它会将回调函数加入到宏任务队列中,并设置延迟时间为 0 毫秒。 - 接下来遇到了另一个
setTimeout
,它也会将回调函数加入到宏任务队列中,并设置延迟时间为 0 毫秒。 - 最后打印出
7
。
最后的考验,明白这个,你就完全理解了V8引擎的事件循环机制了
这里的细节之处就是await
会将后续代码放入微任务队列中
console.log("script start");
async function async1() {
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2 end");
}
async1();
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise(function (resolve, reject) {
console.log("promise");
resolve();
})
.then(() => {
console.log("then1");
})
.then(() => {
console.log("then2");
});
console.log("script end");
- 首先打印
"script start"
。 - 调用
async1()
函数,在这个函数中遇到await async2()
。 async2()
函数被执行,立即打印"async2 end"
。- 回到
async1()
函数,由于await
的存在,async1()
函数会暂停执行,直到async2()
完成,并且await
会将后续代码放入微任务队列中。 - 然后创建了一个 Promise 对象,立即打印
"promise"
。 - 接下来打印
"script end"
。 - 由于
async1()
函数之前被暂停了,现在它可以继续执行,打印"async1 end"
。 - 然后执行 Promise 的
.then()
回调,打印"then1"
和"then2"
。 - 最后执行
setTimeout
的回调,打印"setTimeout"
。
总结
本文深入讲解了V8引擎的事件循环机制,相信看到这里的你一定会有收获的!!!