JS事件循环和宏任务微任务

一 进程与线程

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

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);
  })

欢迎大家在评论区写出执行顺序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容