JavaScript事件循环大揭秘:让异步代码不再“摸鱼”!
前言
JavaScript事件循环机制通过不断检查执行栈和任务队列,先执行同步任务,再处理微任务,最后执行宏任务,确保异步任务有序执行。
话题引入
我们观察这段代码,想必可以很轻松地说出结果。两次a的打印值必然都是1,然后打印bar,因为我们是先执行同步任务,然后执行异步任务。
那这段代码基本原理也是一样的。第一个b和第二个b打印结果显然相同。
关于事件循环机制
我们首先要知道js里面的代码分为同步代码和异步代码。同步代码不耗时,异步代码耗时,当然我们这里指的耗时是相对的。没有绝对不耗时的代码,就像运动是绝对的,而静止是相对的一样。
接下来我们来聊一下进程和线程的概念。举一个例子,首先进程和线程都是时间单位,进程我们以谷歌浏览器打个比方,就好比你去打开一个浏览器页面,这是一个进程,然后这个页面包括很多线程,比如页面渲染,js引擎,http请求都是属于线程的概念。值得注意的是,js的引擎和页面渲染不能同时工作。这也是js单线程工作的一种优点,不然页面显示可能会出问题。
我们看这个例子,假设for循环和定时器执行时间一样,那么结果就会出问题。同一时间如果拿到了两个指令,该听哪个呢?学过java或数据库的同学就知道,有一个锁的概念,但是js里面是没有的。
事件循环机制
我们看这段代码,运行结果应该如何呢?显而易见和一开始的差不多,打印结果是0.我们目前只知道先执行同步代码,然后执行异步代码,但是这里面涉及到的基层原理,大家清楚嘛
首先我们给出微任务和宏任务的定义,它们都是异步任务里的,微任务里最常用的就是我们的promise.then(),宏任务我们常见的有两个定时器。
接下来是我们最最重要的事件循环的步骤啦! 我们首先执行同步代码,开启第一轮事件循环,同步代码执行完毕之后,检查是否存在异步代码,先执行微任务,再进行宏任务(有渲染页面则作为宏任务第一步),这也相当于开启了下一轮宏任务。
例子一
我们举一个例子来说明事件循环机制的具体执行步骤。我们第一步先执行同步任务,我们要注意的是promise函数的调用也属于同步任务。因此先1,然后往下执行,2,两个then作为微任务先存入微任务队列中,然后set函数作为宏任务存入宏任务队列,然后6,这个时候开始微任务,先3然后4,再执行宏任务,这是我们第一轮循环的结束,也是我们第二轮循环的开始。这个时候就输出5.因此结果为126345.
例子二
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);
我们看这段代码,依然按照我们的时间循环机制来看。
- 由上往下看,先执行同步任务,1
- 然后是2
- then存入异步微任务队列
- set存入异步宏任务队列
- 然后7
- 执行微任务3
- 里面的set存入异步宏任务队列
- 执行异步宏任务5,开启第二轮事件循环
- 里面的set存入异步宏任务队列
- 执行剩余的异步宏任务,因为为队列存入形式,因此先4,然后6,也相当于又开启了两轮循环。
- 结果1273546
例子三
在讲解例子三之前,首先我们了解一下async的含义,用于加在函数前,与函数体内await搭配使用,它的出现是为了让promise更加优雅。async函数内await的内容会优先执行。而这个函数本身也是返回一个promise对象,后面仍然可以接then
function a() {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1);
resolve()
}, 1000)
})
}
function b() {
return new Promise((resolve) => {
setTimeout(() => {
console.log(2);
resolve()
}, 2000)
})
}
b()
.then(() => {
return a()
})
// 2 1
async function c() {
await b()
await a()
console.log(3);
}
c()
// 2 1 3
我们看这段代码就可以很清楚的知道它的作用啦!而且我们要明确一点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 end
- async1 end放入微任务执行队列
- set放入宏任务执行队列
- promise
- 两个then放入微任务执行队列
- script end
- 微任务async1 end
- 微任务then1
- 微任务then2
- 宏任务setTimeout
当你看这道题时脑子的思路如果是清晰的,那么恭喜你,事件循环机制你已拿下!
转载自:https://juejin.cn/post/7380283002482196543