详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务
队列在前端中的应用
- 一、队列是什么
- 二、应用场景
- 三、前端与队列:事件循环与任务队列
- 1、event loop
- 2、JS如何执行
- 3、event loop过程
- 4、 DOM 事件和 event loop
- 5、event loop 总结
- 四、宏任务和微任务
- 1、引例
- 2、宏任务和微任务
- (1)常用的宏任务和微任务
- (2)宏任务和微任务的优先级
- (3)代码实现微任务和宏任务
- (4)event loop和DOM渲染
- (5)微任务、宏任务和DOM渲染的关系
- (6)为何微任务更早
- 五、结束语
队列
在日常生活中的应用非常广泛,比如我们最熟悉不过的食堂排队打饭、击鼓传花等等问题。同时,它在前端中的应用也非常广泛,比如,事件循环 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
是先执行同步代码,所以先打印 Hi
和 Bye
,之后执行异步代码,打印出 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
是单线程的;- 异步(
setTimeout
,ajax
等)使用回调,基于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*/
在上面这段代码中,第一个和第二个打印结果是基于同步,我们都知道要打印 100
和 400
,但是第三个和第四个打印结果,理论上按照打印顺序应该是 200
和 300
才是,为什么是打印 300
和 200
呢?这就涉及到一个宏任务和微任务的问题。接下来将对宏任务和微任务进行讲解。
2、宏任务和微任务
(1)常用的宏任务和微任务
名称 | 举例(常用) |
---|---|
宏任务 | script、setTimeout 、setInterval 、setImmediate、Ajax、DOM事件、I/O、UI Rendering |
微任务 | process.nextTick()、Promise、async/await |
上述的 setTimeout
和 setInterval
等都是任务源,真正进入任务队列的是他们分发的任务。
注意: 微任务执行时机比宏任务要早!!
(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
*/
由上图可知,当程序调用栈空闲时,程序会先尝试去进行 DOM
渲染,最后再触发 Event Loop
机制。所以,在上面的这段代码中,程序会先打印同步代码 Hi
和 Bye
,等待同步代码打印完毕后,会再查找 DOM
事件,进行渲染,最后再触发 event loop
。
总结 event loop
和 DOM
渲染的关系:
在程序执行的时候,
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 渲染,便于查看效果)
});
以上这段代码中,浏览器显示效果如下。
在图中可以看出,微任务 promise
在 DOM
渲染前就触发了,所以 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
的关系。在第一个演示中,微任务 Promise
在 DOM
还没有渲染时就触发了,所以微任务都是在 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
、微任务和宏任务有了一个更深的认识。
队列在前端中的应用就讲到这里啦!如有不理解或者文章有误欢迎评论区留言或私信我交流~
关注公众号 星期一研究室 ,第一时间关注学习干货,更多有趣的专栏待你解锁~
如果这篇文章对你有用,记得点个赞加个关注再走哦~
我们下期见!
详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务相关推荐
- ajax是宏任务还是微任务,(滴滴面试)事件循环Event Loop及微任务和宏任务的执行过程详解...
之前一直在面试,对于一些大厂面试题真的还是很注重原理和基础的, 还有就是数据结构和算法这种,校招的话,这些是很重要的, 前天和滴滴的人面试,问的真心觉得不难,而且也都是现在面试前端很常见的问题, 对于 ...
- for循环延时_前端中的事件循环eventloop机制
我们知道 js 是单线程执行的,那么异步的代码 js 是怎么处理的呢?例如下面的代码是如何进行输出的: console.log(1);setTimeout(function() { console.l ...
- 一致性协议raft详解(四):raft在工程实践中的优化
一致性协议raft详解(四):raft在工程实践中的优化 前言 性能优化 client对raft集群的读写 参考链接 前言 有关一致性协议的资料网上有很多,当然错误也有很多.笔者在学习的过程中走了不少 ...
- 详解浏览器缓存 前端开发必会
详解浏览器缓存 缓存可以说是性能优化中简单高效的一种优化方式了.一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷. 对于一个数据请求来 ...
- spring mvc DispatcherServlet详解之前传---前端控制器架构
前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端.前端控制器既可以使用Filter实现 ...
- 详解链表在前端的应用,顺便再弄懂原型和原型链!
链表在前端中的应用 一.链表VS数组 二.JS中的链表 三.前端与链表:JS中的原型链 1.原型是什么? 2.原型链是什么? 3.原型链长啥样? (1)arr → Array.prototype → ...
- 《Android游戏开发详解》一2.18 使用Java API中的对象
本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.18节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.1 ...
- PackageManagerService启动详解(五)之Android包信息体和解析器(中)
PKMS启动详解(五)之Android包信息体和包解析器(中) Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS启动详解(一)之 ...
- JavaCV开发详解之34:使用filter滤镜实现字符滚动和无限循环滚动字符叠加,跑马灯特效制作
javacv实战专栏目录: JavaCV实战专栏文章目录(JavaCV速查手册) 前言 我们在 javaCV开发详解之13:使用FFmpeg Filter过滤器处理音视频中已经简单介绍过如何使用jav ...
最新文章
- rpm安装mysql报错NOKEY_rpm包安装报错: Header V3 RSASHA256 Signature, key ID fd431d51 NOKEY
- 计算机视觉库OpenCV初步了解
- 【18】ASP.NET Core MVC 中的 Model介绍
- python spark hadoop_使用Scala或Python列出存储在Hadoop HDFS上的Spark群集中可用的所有文件?...
- python3纵向输出字符串_python3字符串输出常见面试题总结
- 并发编程-Atomic的compareAndSet
- ebaz s9 zynq linux中关于网络的一些小问题
- 基于USB armory 制作一个USB恶意软件分析器
- hibernate 实现数据库查询
- linux目录蓝色,前言linux系统默认目录颜色是蓝色的,在黑背景下看不清楚,可以通过以下2种方法修改ls查看的颜色。方法:1、拷贝/etc/DIR_COLORS文件为...
- ubuntu下安装2个mysql_Ubuntu下MySQL的手工安装
- SAM-BA和AT91SAM9260连接问题
- Ubuntu系统下如何提交代码到GitHub
- 对普通文件 霍夫曼编解码 matlab,JPEG编解码过程详解(二)
- 京东评论爬虫(详解)
- Django models模块字段注释
- 搞定制作好看icon
- Spire.XLS:一款Excel处理神器
- 财报数据知冷暖:欧洲电信市场整体复苏中
- spring-cloud服务监控
热门文章
- [转]Android 项目的代码混淆,Android proguard 使用说明
- 【专升本计算机】最新甘肃省专升本考试C语言部分复习题带答案
- 【ArcGIS风暴】ArcGIS解决数字化之前创建图层时未定义坐标系而导致数据跑偏的问题
- 将一个正方形分成4个大小一样的小正方形,再将其中一个小正方形分成4个小正方形,如此类推,分割n次是几个正方形?
- Android Studio之编译t提示Invoke-customs are only supported starting with Android O (--min-api 26)
- 主成分分析步骤_多元分析(1)--主成分分析
- 神奇却又随处可见的斐波那契曲线...
- 不同声音的传播速度会一样吗?
- 此内容过于真实,引起强烈舒适
- 你永远都不知道你老公可以多幼稚......