JavaScript事件循环大揭秘:让异步代码不再“摸鱼”!

前言

JavaScript事件循环机制通过不断检查执行栈和任务队列,先执行同步任务,再处理微任务,最后执行宏任务,确保异步任务有序执行。

话题引入

屏幕截图 2024-06-15 130946.png

我们观察这段代码,想必可以很轻松地说出结果。两次a的打印值必然都是1,然后打印bar,因为我们是先执行同步任务,然后执行异步任务。

屏幕截图 2024-06-15 131112.png

那这段代码基本原理也是一样的。第一个b和第二个b打印结果显然相同。

关于事件循环机制

屏幕截图 2024-06-15 131854.png

我们首先要知道js里面的代码分为同步代码和异步代码。同步代码不耗时,异步代码耗时,当然我们这里指的耗时是相对的。没有绝对不耗时的代码,就像运动是绝对的,而静止是相对的一样。

屏幕截图 2024-06-15 132612.png

接下来我们来聊一下进程和线程的概念。举一个例子,首先进程和线程都是时间单位,进程我们以谷歌浏览器打个比方,就好比你去打开一个浏览器页面,这是一个进程,然后这个页面包括很多线程,比如页面渲染,js引擎,http请求都是属于线程的概念。值得注意的是,js的引擎和页面渲染不能同时工作。这也是js单线程工作的一种优点,不然页面显示可能会出问题。

屏幕截图 2024-06-15 133420.png

我们看这个例子,假设for循环和定时器执行时间一样,那么结果就会出问题。同一时间如果拿到了两个指令,该听哪个呢?学过java或数据库的同学就知道,有一个锁的概念,但是js里面是没有的。

事件循环机制

屏幕截图 2024-06-15 133739.png

我们看这段代码,运行结果应该如何呢?显而易见和一开始的差不多,打印结果是0.我们目前只知道先执行同步代码,然后执行异步代码,但是这里面涉及到的基层原理,大家清楚嘛

屏幕截图 2024-06-15 134318.png

首先我们给出微任务和宏任务的定义,它们都是异步任务里的,微任务里最常用的就是我们的promise.then(),宏任务我们常见的有两个定时器。

屏幕截图 2024-06-15 140320.png

接下来是我们最最重要的事件循环的步骤啦! 我们首先执行同步代码,开启第一轮事件循环,同步代码执行完毕之后,检查是否存在异步代码,先执行微任务,再进行宏任务(有渲染页面则作为宏任务第一步),这也相当于开启了下一轮宏任务。

例子一

屏幕截图 2024-06-15 140833.png

我们举一个例子来说明事件循环机制的具体执行步骤。我们第一步先执行同步任务,我们要注意的是promise函数的调用也属于同步任务。因此先1,然后往下执行,2,两个then作为微任务先存入微任务队列中,然后set函数作为宏任务存入宏任务队列,然后6,这个时候开始微任务,先3然后4,再执行宏任务,这是我们第一轮循环的结束,也是我们第二轮循环的开始。这个时候就输出5.因此结果为126345.

屏幕截图 2024-06-15 141418.png

例子二

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. 由上往下看,先执行同步任务,1
  2. 然后是2
  3. then存入异步微任务队列
  4. set存入异步宏任务队列
  5. 然后7
  6. 执行微任务3
  7. 里面的set存入异步宏任务队列
  8. 执行异步宏任务5,开启第二轮事件循环
  9. 里面的set存入异步宏任务队列
  10. 执行剩余的异步宏任务,因为为队列存入形式,因此先4,然后6,也相当于又开启了两轮循环。
  11. 结果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');

 

  1. 执行同步任务,script start
  2. 执行async1函数里面先await async2(),async2 end
  3. async1 end放入微任务执行队列
  4. set放入宏任务执行队列
  5. promise
  6. 两个then放入微任务执行队列
  7. script end
  8. 微任务async1 end
  9. 微任务then1
  10. 微任务then2
  11. 宏任务setTimeout

当你看这道题时脑子的思路如果是清晰的,那么恭喜你,事件循环机制你已拿下!

原文:https://juejin.cn/post/7380283002482196543

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值