最近罗列了一些软件开发基础知识点,计划逐一的、彻底的理解每一个知识点,并为每个知识点写一篇详细的,图文并茂的文章。这篇是关于浏览器环境下 JS 的 Event Loop 机制(如有错误,欢迎指出)。

浏览器线程

我们常说 JS 是单线程语言,但是别忘了常见的浏览器内核可都是多线程的,多个线程间会进行不断通讯,通常会有如下几个线程:

  • GUI 渲染进程
  • JS 引擎线程
  • 定时器线程
  • 事件触发线程
  • 异步 HTTP 请求线程

Microtask 与 Macrotask

在大多数解释 JS Event Loop 的文章中,鲜有谈及 Miscrotask 和 Macrotask 这两个概念,但这两个概念却是非常的重要,我在翻阅 Zone.js Primer 时,里面就经常会提及这两个概念,当时也是看的云里雾里的,这也是我写这篇文章的原因之一。

setTimeout(function () {console.log('timeout1');
}, 0);console.log('start');Promise.resolve().then(function () {console.log('promise1');Promise.resolve().then(function () {console.log('promise2');});setTimeout(function () {Promise.resolve().then(function () {console.log('promise3');});console.log('timeout2')}, 0);
});console.log('done');
复制代码

以上代码最后会输出什么呢?如果你能很快的回答出来,你大概就已经掌握了 Event Loop 的实际运用了,如果回答不出,那可能还得接着往下看。

问题:是先执行 then( ) 中的回调函数呢,还是 setTimeout( ) 中的回调函数呢?

答案:先执行前者。因为 Promise.prototype.then( ) 是 Microtask ,而 setTimeout( ) 是 Macrotask 。至于为什么先执行 Miscrotask ?继续往后看~

在 JS 线程中程序的每一个调用都被看成是一个任务(task) ,所有的任务被分成许多类型且存放在对应类型的队列中,为了方便理解,我把这些任务队列分成三类:

  • Micro-task queue: 存放 microtask 的回调函数。

  • Macro-task queue: 存放 macrotask 的回调函数 。

  • Other-task queue: 这是一个我个人抽象出来队列,实际并不存在,假设该队列用来存放除了 microtask 和 macrotask 外的所有任务。

Microtask 和 Macrotask 的区别就是执行顺序上的区别。简单的说,JS 线程会先处理 other-task queue 上的任务,处理完了之后,再去处理 micro-task queue 上的任务,最后才处理 macro-task queue 上的任务。至于 JS 线程具体的执行细节,后面会详细的进行描述。

以下是常见的 Microtask 和 Macrotask:

  • Microtask :Promise.prototype.then( )、MutationObserver.prototype.observe( ) 等 。

  • Macrotask :setTimeout( )、setImmediate( )、XMLHttpRequest.prototype.onload( ) 等。

JS 线程 Event Loop 的实现

如上,根据个人的理解,我画了一个浏览器环境下 JS 实现 Event Loop 大致模型图,具体含义如下:

1 获取执行的任务,执行步骤 1.1

1.1 判断 other-task queue 中是否有任务,如果有,获取最早的任务然后执行步骤 2 ,否则执行步骤 1.2 。

1.2 判断 micro-task queue 中是否有任务,如果有,获取最早的任务然后执行步骤 2 ,否则执行步骤 1.3 。

1.3 判断 macro-task queue 中是否有任务,如果有,获取最早的任务然后任何执行步骤 2 ,否则执行步骤 3 。

2 将取到的任务放到 call stack 并执行,执行完之后再执行步骤 1 (值得注意的是,在执行的过程中,是会不断的更新所有的 task queue ,因为 call stack 中正在执行的任务内部也可能存在普通任务、microtask 和 macrotask ,执行任务的过程可以理解为一个递归过程,如果无限递归,call stack 上待执行的任务就会不断累积而溢出,这也就是常见的 Maximum call stack size exceeded 错误)。

3 线程会处理其他工作,例如:不断同步「事件触发线程」的状态,一旦有事件触发,即查看触发事件「target」有没有对应事件的监听器任务,如果有,则选中该任务并执行步骤 2 。需要注意的是,并不是只有执行了步骤 1.3 后才会执行当前步骤,JS 线程肯定还会在的某个时候去同步其他线程的状态的。

接下来,如果仔细想,可能会产生一个疑问:JS 进程是如何更新 micro-task queue 和 macro-task queue 这两个队列的呢 ?

根据我的理解,micro-task queue 和 other-task queue 都是“同步”更新的,而 macro-task queue 是“异步”更新。以下是 macro-task queue 更新的具体流程(以 setTimeout 为例):

  1. JS 线程判断某个 macrotask 是一个定时器,将这个定时器同步给定时器线程。
  2. 定时器线程启动从 JS 线程收到的定时器。
  3. JS 线程在某个时候(可能是执行上述步骤 1 的时候)会通过定时器和 http 请求等一些线程来更新 macro-task queue ,即如果以上的定时结束了,JS 线程就可以将对应定时器的回调函数存放到 macro-task queue 中。

如何理解 JS 中的异步

目前普遍对异步的解释可能是:执行调用,如果立即得到结果就是同步调用,否则为异步调用。

在 JS 环境中,我个人其实是不同意这个解释的。

首先,根据以上的解释,setTimeout( )、Promise.prototype.then( ) 、http 请求和各类浏览器事件,这些都被认为是异步的。但我却不这么认为,我认为浏览器事件不是异步的。以下代码便是理由:

// html: <button id="btn">click</button>// js
var btn = document.getElementById('btn');setTimeout(function () {console.log('timeout')
}, 0);Promise.resolve().then(function () {console.log('promise');
});btn.addEventListener('click', function () {console.log('click');
});btn.click();console.log('done');
复制代码

如果浏览器事件是异步的,不管后续会打印出什么,第一个打印的必然是 done ,而实际的打印结果为:click done promise timeout

也就是说,JS 认为浏览器事件并非异步。

由此,我个人对异步的解释是:在满足调用所需的外在条件的情况下,执行调用,立即获得结果的就为同步调用,否则为异步调用。

根据这个理解,当我们发起的一个 http 请求时,假设服务器以光速返回请求结果,XMLHttpRequest 对象的 onload 方法会立即执行吗?,显然不会,所以 http 请求为异步调用。这也是为什么我在以上分析 Event Loop 中的任务队列时并没有将 event-task queue 拎出来的原因。因此,对于异步调用的判断可以是这样:如果某个调用属于 microtask 或是 macrotask 中的其中一个,那么这个调用就是异步调用。

题外话

有人可能会注意到,这篇文章经常出现「我认为」和「我理解」,这并非是我对自己不自信,而是我想表达一个看法:在翻阅别人的技术文章的时候,务必保持独立思考的能力,就算文章的作者是业界有名的大牛,也不能没缘由的「深信不疑」,对对应的技术点务必在自个脑中里建立一个可以自圆其说的模型。至于我为什么会表达这个看法,是因为我找翻阅大量的过程中,发现大多数关于 JS Event Loop 的文章或多或少都有一些粗糙或是错误,如果我只看其中的某一篇,我很大的概率会有建立一个错误的 Event Loop 模型。当然,就我当前的理解,还是可能会有些许错误。Anyway ,还是那句话:保持独立思考,与各位共勉。

Done.?

参考链接

  • Philip Roberts: What the heck is the event loop anyway? | JSConf EU 2014
  • Tasks, microtasks, queues and schedules
  • HTLM5 EVENT LOOP DEFINITIONS

彻底理解 JS Event Loop(浏览器环境)相关推荐

  1. Node.js event loop 和 JS 浏览器环境下的事件循环的区别

    Node.js  event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...

  2. Js Event Loop

    Js Event Loop 本文主要介绍 Node 中的事件循环 参考 官网 (什么是 Event Loop? - 阮一峰的网络日志 (ruanyifeng.com)) What is the Eve ...

  3. Event loop/浏览器的事件循环机制

    一.Event loop引入 1.js为什么是单线程的? 之前我们已经学习了浏览器的关键渲染路径,知道了网页内容交给浏览器后浏览器是如何解析.渲染.绘制的,也提到了浏览器是多线程的,js是单线程的,那 ...

  4. 【译】node js event loop part 1.1

    原文 先说1.1总揽: Reactor模式 Reactor模式中的协调机制Event Loop Reactor模式中的事件分离器Event Demultiplexer 一些Event Demultip ...

  5. JS——检测当前浏览器环境为微信OR企业微信

    let ua = navigator.userAgent.toLowerCase(); let isWx = ua.match(/MicroMessenger/i) == "micromes ...

  6. 记录JS event Loop机制及Node v8事件执行机制

    转载于:https://www.cnblogs.com/liujiekun/p/11295536.html

  7. Node.js Event loop 图解

    直接上自己制作的流程图

  8. 为什么JS是单线程?JS中的Event Loop(事件循环)?JS如何实现异步?setimeout?

    https://segmentfault.com/a/1190000012806637 https://www.jianshu.com/p/93d756db8c81 首先,请牢记2点: (1) JS是 ...

  9. JS逆向之浏览器补环境详解

    JS逆向之浏览器补环境详解 "补浏览器环境"是JS逆向者升职加薪的必备技能,也是工作中不可避免的操作. 为了让大家彻底搞懂 "补浏览器环境"的缘由及原理,本文将 ...

最新文章

  1. 我是一个SDN控制器
  2. 祝各位节日快乐!20151111
  3. 世界上最牛的网络设备,价格低廉,其貌不扬......
  4. python教程:几个基础类型循环删除
  5. XML 新手最佳入门教程
  6. blender的汉化方法!
  7. POJ 1502 MPI Maelstrom 最短路
  8. 找到一个或多个多重定义的符号_初中数学之相反数,总结规律,学会多重符号的化简...
  9. oracle 10g for linux
  10. 重庆自考学历计算机应用基础考试,2017年自考计算机应用基础模拟试题1
  11. 通过密钥 SFTP(一)
  12. 按键消抖(并联0.1uf电容)
  13. 转:最详细的JavaScript 教程,入门级都能看懂
  14. macOs 静默安装dmg文件
  15. Unity笔记-29-ARPG游戏项目-01-第三人称相机
  16. 江开计算机应用基础第二次形考,江开计算机应用基础第二次形考作业
  17. 路由算法(Dijkstra, Bellman-Ford算法)
  18. Python实现钉钉/企业微信自动打卡
  19. 淘宝网店应该怎么样去做好宝贝SEO优化?
  20. 详解SQL中几种常用的表连接方式!

热门文章

  1. 兼容PD快充5V-12V高压给两节串联8.4V锂电池1A充电芯片电路图
  2. TPS61088RHLR升压芯片
  3. Ubuntu16.04比较好的一系列软件安装介绍
  4. 计算两个时间之间的工作时长
  5. TÜV莱茵提醒: 欧盟发布医疗器械在MDR宽限期的重大变更指南
  6. 什么是欧盟emark认证?
  7. 随机性测试软件,5种随机性检测方法
  8. Excel随机生成手机号码
  9. HTML新手入门(一)
  10. android x86版本区分,Android X86 4.3(JB-x86)测试版本20130725发布