(转)JS事件循环和宏任务和微任务

tech2024-04-12  4

一 进程与线程

进程和线程的概念用较为官方的术语描述来说是这样的

 

1.进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位) 2.线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

用LOL来比喻的话(举例子可能不太贴切,因为本人比较喜欢玩lol 所以用这个举例子让自己来加深印象)

 

一个进程就是一局游戏,每一局游戏都要有野怪小兵这些资源; 每局游戏都相互独立互不干涉; 一个线程就是一个英雄; 一局游戏里有多个英雄(一个进程有多个线程); 英雄之间共享资源

映射关系为

 

一局游戏的野怪和小兵 > 系统分配的内存 每一局游戏相互独立 > 进程之间相互独立 打团战 -> 多个线程在进程中协作完成任务 一局游戏可能一个玩也可能多个人玩>一个进程由一个或多个线程组成 英雄之间共享野怪小兵谁都可以吃 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

二 浏览器是多进程的

浏览器可以运行是因为系统给分配了资源和内存,浏览器每一个tab页都需要资源和内存,所以每一个tab页面都对应至少一个进程,在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了 (所以每一个Tab标签对应一个进程并不一定是绝对的) 知道了浏览器是多进程后,再来看看它到底包含哪些进程:(为了简化理解,仅列举主要进程)

1. Browser进程

 

浏览器的主进程(负责协调、主控),只有一个。 负责浏览器界面显示,与用户交互。如前进,后退等 负责各个页面的管理,创建和销毁其他进程 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上 网络资源的管理,下载等

2. 第三方插件进程

 

每种类型的插件对应一个进程,仅当使用该插件时才创建

3. GPU进程

 

最多一个,用于3D绘制等

4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的)

 

默认每个Tab页面一个进程,互不影响。 主要作用为 页面渲染,脚本执行,事件处理等

三 浏览器渲染进程

作为前端开发工程师我们最主要的关注点还是渲染进程。 可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行,渲染进程是多线程的 接下来重点分析这个进程的常驻线程。

1.GUI渲染进程

 

1.负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 2.当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。 3.注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

2.JS引擎线程

 

1.也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎) 2.JS引擎线程负责解析Javascript脚本,运行代码。 3.JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序 4.同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3.事件处理线程

 

1.归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助) 2.当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中 3.当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理 4.注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4.定时器线程

 

1.传说中的setInterval与setTimeout所在线程 2.浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确) 3.因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行) 4.注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

5.http异步请求线程

 

1.在XMLHttpRequest在连接后是通过浏览器新开一个线程请求 2.将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

image

四 事件循环

上文说了几个线程

 

js引擎线程 事件触发线程 定时触发器线程

然后再理解一个概念:

 

JS分为同步任务和异步任务 1.同步任务都在主线程上执行,形成一个执行栈 2.主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件 3.一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行

image

 

image

 

主线程运行时会产生执行栈,栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕) 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调 如此循环 注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件

五 宏任务微任务

上文中将JS事件循环机制梳理了一遍,在ES5的情况是够用了,但是在ES6盛行的现在,仍然会遇到一些问题,譬如下面这题:

 

console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');

它的正确执行顺序是这样子的:

 

script start script end promise1 promise2 setTimeout

为什么呢?因为Promise里有了一个一个新的概念:microtask。微任务 JS中分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task。 它们的定义?区别?简单点可以按如下理解:

 

macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行) 每一个task会从头到尾将这个任务执行完毕,不会执行其它 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染

 

microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务 也就是说,在当前task任务后,下一个task之前,在渲染之前 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

分别很么样的场景会形成macrotask和microtask呢?

 

macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask) microtask:Promise,process.nextTick等

再根据线程来理解下:

 

macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护 microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护(这点由自己理解+推测得出,因为它是在主线程下无缝执行的)

所以,总结下运行机制:

 

执行一个宏任务(栈中没有就从事件队列中获取) 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行) 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

image

参考文章 全面梳理JS引擎的运行机制https://mp.weixin.qq.com/s?__biz=MzUzMjA3MTI2NQ==&mid=2247485214&idx=1&sn=12decb0ba6b6060d6b9f88ee6c227d7e&chksm=fab99110cdce1806c508950bf2cdb1b09c45fb9a48acbce868694ad980c112ddcee671b0a3f6&mpshare=1&scene=24&srcid=&sharer_sharetime=1585841105377&sharer_shareid=762717275fedf766441eee549354e3af&key=25482536a2868f95a747be1bba6a2a0f336defbbf8d45116a1dc006e3119777adad66c433cd6f4b685e308f086f164107dd3aa1c6b2eedb4d6e3007bab957877ea458e842beb61384aec253a8d3113a5&ascene=14&uin=MTc1MjM1NzM4MQ%3D%3D&devicetype=Windows+7&version=62080079&lang=zh_CN&exportkey=AewYYl61ye1z6y1NiRjiJEQ%3D&pass_ticket=C4is0r6nXq%2FvPIwtVRO8UIjTQ7xNdzevdyWz58B0vV70j5JGHCTivvcazNg4WduX](https://mp.weixin.qq.com/s?__biz=MzUzMjA3MTI2NQ==&mid=2247485214&idx=1&sn=12decb0ba6b6060d6b9f88ee6c227d7e&chksm=fab99110cdce1806c508950bf2cdb1b09c45fb9a48acbce868694ad980c112ddcee671b0a3f6&mpshare=1&scene=24&srcid=&sharer_sharetime=1585841105377&sharer_shareid=762717275fedf766441eee549354e3af&key=25482536a2868f95a747be1bba6a2a0f336defbbf8d45116a1dc006e3119777adad66c433cd6f4b685e308f086f164107dd3aa1c6b2eedb4d6e3007bab957877ea458e842beb61384aec253a8d3113a5&ascene=14&uin=MTc1MjM1NzM4MQ%3D%3D&devicetype=Windows+7&version=62080079&lang=zh_CN&exportkey=AewYYl61ye1z6y1NiRjiJEQ%3D&pass_ticket=C4is0r6nXq%2FvPIwtVRO8UIjTQ7xNdzevdyWz58B0vV70j5JGHCTivvcazNg4WduX

最后附上几道题

 

async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }) console.log('script end');

 

console.log('1'); setTimeout(function() { console.log('2'); new Promise(function(resolve) { console.log('3'); resolve(); }).then(function() { console.log('4'); }) }) new Promise(function(resolve) { console.log('5'); resolve(); }).then(function() { console.log('6'); }) setTimeout(function() { console.log('7'); }) setTimeout(function() { console.log('8'); new Promise(function(resolve) { console.log('9'); resolve(); }).then(function() { console.log('10'); }) }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12'); }) console.log('13');

 

const promise = new Promise((resolve, reject) => { console.log("1"); setTimeout(() => { console.log("2"); setTimeout(() => { console.log('4'); }) resolve('success'); }, 1000) }) console.log('3'); promise.then((res) => { return new Error('error!!!') }).then((res) => { console.log('then:', res) }).catch((err) => { console.log('catch:', err) }) promise.then((res) => { console.log(res) }).catch((err) => { console.log('catch:', err); })

链接:https://www.jianshu.com/p/c3756648e921

最新回复(0)