详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

简介: 该文章详细讲解了队列数据结构在前端开发中的应用,并深入探讨了JavaScript的事件循环机制,区分了宏任务和微任务的执行顺序及其对前端性能的影响。

队列在前端中的应用

队列 在日常生活中的应用非常广泛,比如我们最熟悉不过的食堂排队打饭、击鼓传花等等问题。同时,它在前端中的应用也非常广泛,比如,事件循环 Event loop 、JS异步中的任务队列。

所以呢,对于前端来说, 队列 结构是一个必学的知识点。在接下来的这篇文章中,将讲解关于 队列 在前端中的应用。

一、队列是什么

队列是一种先进先出(FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。

二、应用场景

  • 需要先进先出的场景。
  • 比如:食堂排队打饭、火车站排队买票、JS异步中的任务队列、计算最近请求次数……。

三、前端与队列:事件循环与任务队列

1、event loop

event loop,也被称为事件循环事件轮询。因为JS是单线程运行的,且异步需要基于回调来实现,所以, event loop 就是异步回调的实现原理。

2、JS如何执行

JS在程序中的执行遵循以下规则:

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码的执行
  • 先把同步代码执行完,再执行异步

一起来看一个实例:

console.log('Hi');

setTimeout(function cb1(){
   
    console.log('cb1'); //cb1 即callback回调函数
}, 5000);

console.log('Bye');

//打印顺序:
//Hi
//Bye
//cb1

从上例代码中可以看到, JS 是先执行同步代码,所以先打印 HiBye ,之后执行异步代码,打印出 cb1

以此代码为例,下面开始讲解 event loop 的过程。

3、event loop过程

对于上面这段代码,执行过程如下图所示。

在这里插入图片描述

从上图中可以分析出这段代码的运行轨迹。首先 console.log('Hi') 是同步代码,直接执行并打印出 Hi 。接下来继续执行定时器 setTimeout ,定时器是异步代码,所以这个时候浏览器会将它交给 Web APIs 来处理这件事情,因此先把它放到 Web APIs 中,之后继续执行 console.log('Bye')console.log('Bye') 是同步代码,在调用堆栈 Call Stack 中执行,打印出 Bye

到这里,调用堆栈 Call Stack 里面的内容全部执行完毕,当调用堆栈的内容为空时,浏览器就会开始去任务队列寻找下一个任务,此时任务队列就会去 Web API 里面寻找任务,遵循先进先出原则,找到了定时器,且定时器里面是回调函数 cb1 ,于是把回调函数 cb1 传入任务队列中,此时 Web API 也空了,任务队列里面的任务就会传入到调用堆栈里Call Stack 里执行,最终打印出 cb1

4、 DOM 事件和 event loop

先来看两段代码。

console.log('Hi');

setTimeout(function cb1(){
   
    console.log('cb1'); //cb 即 callback
}, 5000);

console.log('Bye');

/*
 输出结果:
Hi
Bye
cb1
*/
<button id = "btn1">提交</button>

<script>   
    console.log('Hi');

    document.getElementById('btn1').click(function(e){
    
        console.log('button clicked');
    });

    console.log('Bye');
</script>

/*
 输出结果:
Hi
Bye
button clicked
*/

以上这两段代码中,第一段是关于 setTimeout 的事件循环,第二段是关于 DOM 事件的事件循环。那有小伙伴就会有疑问说, DOM 事件不是异步操作吗,为什么输出结果依然是在最后呢?

其实, DOM 事件确实不是异步操作,但是它也使用回调,基于 event loop 事件循环机制,所以当我们点击的时候,会触发 DOM 事件,并进行打印。

总结下 DOM 事件和 event loop 的区别:

  • JS 是单线程的;
  • 异步( setTimeoutajax 等)使用回调,基于 event loop
  • DOM 事件不是异步,但也使用回调,基于 event loop

5、event loop 总结

初阶认识完event loop后,来做个总结:

总结event loop 过程1

  • 同步代码,一行一行放在 Call Stack 执行;
  • 遇到异步,会先“记录”下,等待时机(定时、网络请求);
  • 时机到了,就移动到 Callback Queue

总结event loop 过程2

  • 如果 Call Stack 为空(即同步代码执行完),则 event Loop 开始工作;
  • 轮询查找 Callback Queue ,如果有则移动到 Call Stack 执行;
  • 然后继续轮询查找(跟永动机一样,不断循环查找)。

四、宏任务和微任务

1、引例

我们先来看一段代码。

console.log(100);
setTimeout(() => {
   
    console.log(200);
});
Promise.resolve().then(() => {
   
    console.log(300);
});
console.log(400);
/**
 * 打印结果:
 * 100
 * 400
 * 300
 * 200
 */

在上面这段代码中,第一个和第二个打印结果是基于同步,我们都知道要打印 100400 ,但是第三个和第四个打印结果,理论上按照打印顺序应该是 200300 才是,为什么是打印 300200 呢?这就涉及到一个宏任务和微任务的问题。接下来将对宏任务和微任务进行讲解。

2、宏任务和微任务

(1)常用的宏任务和微任务

名称 举例(常用)
宏任务 script、setTimeout 、setInterval 、setImmediate、Ajax、DOM事件、I/O、UI Rendering
微任务 process.nextTick()、Promise、async/await

上述的 setTimeoutsetInterval 等都是任务源,真正进入任务队列的是他们分发的任务。

注意: 微任务执行时机比宏任务要早!!

(2)宏任务和微任务的优先级

优先级

  • setTimeout = setInterval 一个队列
  • setTimeout > setImmediate
  • process.nextTick > Promise

(3)代码实现微任务和宏任务

for(const macroTask of macroTaskQueue){
   
    handleMacroTask();
    for(const microTask of microTaskQueue){
   
        handleMicroTask();
    }
}

(4)event loop和DOM渲染

在上面的主题三第4点中讲过, DOM 事件基于回调,也是基于 event loop 机制的。那DOM事件在程序执行到什么时候,才会渲染呢?

同样来看这段代码。

<button id = "btn1">提交</button>

<script>   
    console.log('Hi');

    document.getElementById('btn1').click(function(e){
   
        console.log('button clicked');
    });

    console.log('Bye');
</script>

/*
 输出结果:
Hi
Bye
button clicked
*/

event loop 和 DOM渲染

由上图可知,当程序调用栈空闲时,程序会先尝试去进行 DOM 渲染,最后再触发 Event Loop 机制。所以,在上面的这段代码中,程序会先打印同步代码 HiBye ,等待同步代码打印完毕后,会再查找 DOM 事件,进行渲染,最后再触发 event loop

总结 event loopDOM 渲染的关系:

  • 在程序执行的时候, JS 是单线程的,且和 DOM 渲染共用一个线程;

  • 所以 JS 在执行的时候,得留一些时机提供给 DOM 渲染。

  • 每次 Call Stack 清空(即每次轮询结束),表示同步任务执行完成;

  • 程序会一直给 DOM 重新渲染的机会, DOM 结构如有改变则重新渲染;

  • 然后再去触发下一次 Event Loop

(5)微任务、宏任务和DOM渲染的关系

先了解微任务、宏任务和 DOM 渲染的关系:

  • 宏任务: DOM 渲染触发,如 setTimeout
  • 微任务: DOM 渲染触发,如 Promise

我们先来演示现象,再追究其原理。

1)演示1

const $p1 = $('<p>一段文字</p>');
const $p2 = $('<p>一段文字</p>');
const $p3 = $('<p>一段文字</p>');
$('#container')
            .append($p1)
            .append($p2)
            .append($p3);

//微任务:DOM 渲染前触发
Promise.resolve().then(() => {
   
    console.log('length', $('#container').children().length);
    alert('Promise then');
    //(alert 会阻断 js 执行, 也会阻断 DOM 渲染,便于查看效果)
});

以上这段代码中,浏览器显示效果如下。

微任务

在图中可以看出,微任务 promiseDOM 渲染前就触发了,所以 DOM 对应的文字还没显示时, Promise 就已经打印。

2)演示2

const $p1 = $('<p>一段文字</p>');
const $p2 = $('<p>一段文字</p>');
const $p3 = $('<p>一段文字</p>');
$('#container')
            .append($p1)
            .append($p2)
            .append($p3);

//宏任务:DOM 渲染后触发
setTimeout(() => {
   
    console.log('length1', $('#container').children().length);
    alert('SetTimeout');
    //(alert 会阻断 js 执行, 也会阻断 DOM 渲染,便于查看效果)
});

以上这段代码中,浏览器显示效果如下。

宏任务

在图中可以看出,当 DOM 对应的文字已经显示时, setTimeout 弹框才出现,所以宏任务 setTimeout 是在 DOM 渲染后(即 DOM 渲染并显示结束)才触发。

讲到这里,回到我们前面所说的知识点。

  • 宏任务: DOM 渲染触发,如 setTimeout
  • 微任务: DOM 渲染触发,如 Promise

从上面的演示后,相信大家应该明白了微任务、宏任务和 DOM 的关系。在第一个演示中,微任务 PromiseDOM 还没有渲染时就触发了,所以微任务都是在 DOM 渲染前触发。在第二个演示中,宏任务 setTimeout 在文字显示结束后才触发 alert ,所以微任务都是在 DOM 渲染后才进行触发。

(6)为何微任务更早

理解完微任务和宏任务与DOM的关系后,我们也大致基本了解了为什么微任务比宏任务更早。接下来我们在从 eventloop 层面来看,为什么微任务会比宏任务更早,为什么会在DOM渲染前就开始触发呢?

先用一张图来表示。

微任务宏任务

微任务在执行时不会经过 Web APIs ,它会把它放到一个叫做 micro task queue(即宏任务队列)当中。且微任务是ES6` 语法规定的,宏任务是由浏览器规定的,所以它会比宏任务更早。

到这里,我们讲完了 event loop 以及与其相关的宏任务和微任务,下面我们再用一张图来总结实际运用的执行顺序。

微任务宏任务

从上图中可以得出结论:

第一步,程序先程序 Call Stack 里面的内容,待 Call Stack 清空时,执行当前的微任务;

第二步,程序找到微任务队列的任务,执行微任务;

第三步,待微任务执行完毕后,尝试执行DOM渲染;

第四步DOM 渲染结束后,触发 event loop ,执行宏任务。

五、结束语

队列在前端中的应用可以算是很非常频繁了。基本上我们写的异步函数在执行过程中,都会涉及到事件循环问题。且在前端的面试当中,经常会被问到 event loop 、事件循环或者事件轮询是什么,很多面试者就很容易在这块内容吃亏。相信通过上文的学习,大家都对 eventloop 、微任务和宏任务有了一个更深的认识。

队列在前端中的应用就讲到这里啦!如有不理解或者文章有误欢迎评论区留言或私信我交流~

  • 关注公众号 星期一研究室 ,第一时间关注学习干货,更多有趣的专栏待你解锁~

  • 如果这篇文章对你有用,记得点个赞加个关注再走哦~

  • 我们下期见!🥂🥂🥂

相关文章
|
10天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
6天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2506 14
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1519 14
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
8天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
530 13
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19282 30
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18836 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17524 13
Apache Paimon V0.9最新进展
|
8天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
457 48
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
353 4
叮咚!您有一份六大必做安全操作清单,请查收
|
2天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。