当前位置:网站首页>浏览器事件循环
浏览器事件循环
2022-06-26 19:52:00 【猫猫的叮当】
学习JS,Event Loop是一个绕不开的点。JS 的异步执行逻辑依赖 Event Loop 机制,但是这套机制却是定义在 HTML 标准中的。因为 Event Loop 本身并不属于 ES 层面的功能,是宿主环境给脚本提供了这一机制,才让脚步有了异步执行的能力。根据JS宿主环境的不同,可以分为浏览器的事件循环和node的事件循环,两者之间会有一些不同。这里我们只讲浏览器的事件循环。
一、为什么要有事件循环
浏览器运行过程中会同时面对多种任务,用户交互事件(鼠标、键盘)、网络请求、页面渲染等。而这些任务不能是无序的,必须有个先来后到,浏览器内部需要一套预定的逻辑来有序处理这些任务,因此浏览器事件循环诞生了,再次强调,是浏览器事件循环,不是javascript事件循环,js只是浏览器事件循环的参与者。
二、事件循环是什么
浏览器把任务区分成了 宏任务 和 微任务 或者叫 外部任务 和 内部任务 ,内部任务可以理解为js内部处理的任务,外部任务可以认为是浏览器处理的任务。
外部队列/宏任务队列(Task Queue)回调
也可以叫宏任务队列,浏览器中的外部事件源包含以下几种:
dom操作(页面渲染)、用户交互(鼠标、键盘)、网络请求(Ajax等)、History API操作(history.back、history.go…)、定时器(setTimeout)
这些外部事件源可能很多,为了方便浏览器厂商优化,HTML标准中明确指出一个事件循环有一个或多个外部队列,而每一个外部事件源都有一个对应的外部队列。不同的时间源之间可以有不同的优先级(例如在网络时间和用户交互之间,浏览器可以优先处理鼠标行为,从而让用户感觉更加流畅)。
内部队列/微任务队列(Microtask Queue)回调
也可以叫微任务队列,指的就是javascript语言内部的事件队列,在HTML标准中,并没有明确规定这个队列的事件源,通常认为有以下几种:
Promise的成功(.then)与失败(.catch)
MutationObserver
Object.observe(已废弃)
以上三种除了第一个,其他两个可以认为没有,实际上我们js中能够使用的就只有promise。
每一个事件循环,从外部任务队列中拿出一个来执行,执行完一个外部任务后立即执行内部任务队列中所有内部任务(清空),然后浏览器执行一次渲染,然后再次循环。
下面展示两段代码
1.
// 以下代码会得到什么样的输出结果?
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
});
}, 0);
Promise.resolve().then(function() {
console.log('4');
}).then(function() {
console.log('5');
});
console.log('6');
//执行顺序:164523
1.由于执行当前js代码这个任务是一个宏任务,因此首先输出的是"1",
2.继续执行遇到setTimeou,由于setTimeout是一个外部事件源,它内部的代码会被push到TaskQueue中等待下一次事件循环再执行,
3.当执行到promise的 then 或 catche的时候会将他们按顺序追加到本轮事件循环的末尾,
再继续往下执行输出6,宏任务完成后清空微任务队列中的任务,继而输出4、5
4.如果有的话,执行渲染任务后,本次事件循环结束
5.开始执行下一个宏任务,也就是第一个setTimeout中的代码块,输出2,然后将promise.then添加到本轮循环末尾
6.清空微任务,输出3
2.
console.log(1)
setTimeout(() => console.log(2), 200)
setTimeout(() => {
console.log(3)
setTimeout(() => console.log(4), 50)
}, 100)
new Promise(resolve => {
console.log(5)
resolve()
}).then(() => {
console.log(6)
})
setTimeout(() => {
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
})
console.log(9)
//执行顺序:159678342
1、首先,这段代码会作为宏任务而被添加到宏任务队列里面。这时候,宏任务队列只有一个任务,主线程为空,执行这个任务。
2、console.log(1)入栈,打印1,执行完毕,出栈
3、setTimeout入栈,挂起,等待200ms后将回调函数添加到宏任务队列
4、setTimeout入栈,挂起,等待100ms后将回调函数添加到宏任务队列
5、promise入栈,打印5,然后将then添加到微任务队列
6、setTimeout入栈,挂起,等待4ms后将回调函数加入到宏任务队列。这里提一嘴,HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒。
7、console.log(9)入栈,打印9,执行完毕,出栈
8、主线程为空,检查微任务队列是否有函数,发现有,入栈执行,打印6
到这里,不考虑其他的渲染什么的,本次事件循环就结束了,这时候的打印结果就是1 5 9 6 。
主线程又空了,事件轮询模块会一直轮询宏任务队列是否有任务可以执行。而很明显,在三个挂起的setTimeout里面,第六步的setTimeout是最快将
回调函数添加到宏任务队列的。这时候就可以进行下一个事件循环了。
9、循环开始,首先会打印7,然后将then添加到微任务队列,因为本次循环没有其他事做了,接着就执行微任务队列里的任务,打印8
10、继续轮询,而第四步比第三步更快,先打印3,又发现了setTimeout,继续挂起
而这时候第十步和地三步的哪个更快呢?这就要考虑在第三步到第十步之前有没有耗时的任务了。我们这里并没有什么耗时任务,所以第十步依然会比
第三步先执行。打印4,最后打印2 。整个代码就执行完毕了。
总结:微任务队列会在当前事件循环结束之前清空,而宏任务只有在下一次事件循环的时候才会被执行。自然微任务就会比宏任务优先了。这里说的优先也只是在一个事件循环内。
下面是关于宏观队列和微观队列的一些理解
每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是优先级比宏任务高,且与微任务所处的代码位置无关
setTimeout(()=>{
console.log('0');
},0);
new Promise((resolve,reject)=>{
console.log('1');
resolve();
}).then(()=>{
console.log('2');
new Promise((resolve,reject)=>{
console.log('3');
resolve();
}).then(()=>{
console.log('4');
}).then(()=>{
console.log('5');
})
}).then(()=>{
console.log('6');
})
new Promise((resolve,reject)=>{
console.log('7');
resolve();
}).then(()=>{
console.log('8');
})
先看同步,再看回调
开头一个定时器,将0压入宏队列
new Promise中executor同步执行,直接输出1
接着执行rresolve(),状态改变,调用then(),将成功回调函数压入微队列(2)
由于输出2这里还没执行,可以先不管其下的new Promise,此时第一个then()还没执行结束因此第二个then()也还没开始执行
然后执行下一个new Promise,直接将7输出,然后立马执行resolve(),改变状态后将then()里面成功的回调压入微队列(8)
此时初始化代码全部执行完毕
输出:1 7
宏队列:[0]
微队列:[2,8]
执行微队列中的回调
输出2,console.log(‘2’);执行完后new Promise,直接输出3,立马执行resolve()改变状态后then()成功回调压入微队列(4)。由于此时4还未执行,因此其后输出5的回调不能压入微队列,而是放入缓存,由于状态改变,其后的then()已经执行完了,因此将外层下一个then的回调压入微队列(6)
输出:1 7 2 3
宏队列:[0]
微队列:[8,4,6]
执行输出8,此步不影响其他操作,接着输出4,将其后的回调压入微队列(5)
输出:1 7 2 3 8 4
宏队列:[0]
微队列:[6,5]
最后就是:
输出:1 7 2 3 8 4 6 5 0
宏队列:[]
微队列:[]
边栏推荐
- Daily basic use of alicloud personal image warehouse
- Project practice 5: build elk log collection system
- Convex hull problem
- Tiktok practice ~ sharing module ~ copy short video link
- 转:实事求是
- 回溯思路详解
- 自己创建一个时间拦截器
- The goal you specified requires a project to execute but there is no POM
- Installation and use of logstash
- Refresh the strong pointer assignment problem in the HP-UX system of Sanguan
猜你喜欢
Installation and use of logstash
Super VRT
IK word breaker
Solidity - 合约继承子合约包含构造函数时报错 及 一个合约调用另一合约view函数收取gas费用
西瓜书重温(七): 贝叶斯分类器(手推+代码demo)
Disruptor本地线程队列_使用transProcessor处理器和WorkPool两种方式进行消费对比---线程间通信工作笔记005
Unity——Mathf. Similarities and differences between atan and atan2
MySQL - subquery usage
Daily basic use of alicloud personal image warehouse
Tiktok practice ~ search page ~ scan QR code
随机推荐
C# 练习。类列表加记录,显示记录和清空记录
Analysis on development technology of NFT meta universe chain game system
Development principle analysis and source code of dapp-lp single and dual currency liquidity pledge mining system
知識點總結
Why don't I recommend going to sap training institution for training?
Some cold knowledge about QT database development
网上办理中金财富开户安全吗?
Solidity - 合约继承子合约包含构造函数时报错 及 一个合约调用另一合约view函数收取gas费用
stm32和电机开发(直流有刷电机和步进电机)
Reading notes: process consulting III
转:苹果CEO库克:伟大的想法来自不断拒绝接受现状
[kubernetes] kubernetes principle analysis and practical application (under update)
品达通用权限系统(Day 3~Day 4)
Résumé des points de connaissance
Unity——Mathf. Similarities and differences between atan and atan2
Minimum spanning tree, shortest path, topology sorting, critical path
Jsonutils tool class (based on Alibaba fastjson)
C primer plus学习笔记 —— 3、字符的IO(输入/输出)
动态规划111
好物推薦:移動端開發安全工具