深入分析JavaScript 事件循环(Event Loop)


Posted in Javascript onJune 19, 2020

事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑。

众所周知,JS是单线程的,即同一时间只能运行一个任务。一般情况下这不会引发问题,但是如果我们有一个耗时较多的任务,我们必须等该任务执行完毕才能进入下一个任务,然而等待的这段时间常常让我们无法忍受,因为我们这段时间什么都不能做,包括页面也是锁死状态。

好在,时代在进步,浏览器向我们提供了JS引擎不具备的特性:Web API。Web API包括DOM API、定时器、HTTP请求等特性,可以帮助我们实现异步、非阻塞的行为。我们可以通过异步执行任务的方法来解决单线程的弊端,事件循环为此而生。

提问QAQ:为什么JavaScript是单线程的?

多个线程表示您可以同时独立执行程序的多个部分。确定一种语言是单线程还是多线程的最简单方法是看它拥有有多少个调用堆栈。JS 只有一个,所以它是单线程语言。

将JS设计为单线程是由其用途运行环境等因素决定的,作为浏览器脚本语言,JS的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。同时,单线程执行效率高。

1. Event Loop旧印象

大家熟悉的关于事件循环的机制说法大概是:主进程执行完了之后,每次从任务队列里取一个任务执行。如图所示,所有的任务分为同步任务和异步任务,同步任务直接进入任务队列-->主程序执行;异步任务则会挂起,等待其有返回值时进入任务队列从而被主程序执行。异步任务会通过任务队列的机制(先进先出的机制)来进行协调。具体如图所示:

深入分析JavaScript 事件循环(Event Loop)

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们所熟悉的Event Loop (事件循环)。但是promise出现之后,这个说法就不太准确了。

2. Event Loop 后印象

2.1 理论

这里首先用一张图展示JavaScript的事件循环:

深入分析JavaScript 事件循环(Event Loop)

直接看这张图,可能黑人问号已经出现在同学的脑海。。。

这里将task分为两大类,分别是macroTask(宏任务)和microTask(微任务).一次事件循环:先运行macroTask队列中的一个,然后运行microTask队列中的所有任务。接着开始下一次循环(只是针对macroTask和microTask,一次完整的事件循环会比这个复杂的多)。

那什么是macroTask?什么是microTask呢?

JavaScript引擎把我们的所有任务分门别类,一部分归为macroTask,另外一部分归为microTack,下面是类别划分:

macroTask:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

microTask:

  • process.nextTick
  • Promise
  • Object.observe
  • MutationObserver

我们所熟悉的定时器就属于macroTask,仅仅了解macroTask的机制还是不够的。为直观感受两种队列的区别,下面上代码进行实践感知。

2.2 实践

以setTimeout、process.nextTick、promise为例直观感受下两种任务队列的运行方式。

console.log('main1');

process.nextTick(function() {
 console.log('process.nextTick1');
});

setTimeout(function() {
 console.log('setTimeout');
 process.nextTick(function() {
 console.log('process.nextTick2');
 });
}, 0);

new Promise(function(resolve, reject) {
 console.log('promise');
 resolve();
}).then(function() {
 console.log('promise then');
});

console.log('main2');

别着急看答案,先以上面的理论自己想想,运行结果会是啥?

最终结果是这样的:

main1
promise
main2
process.nextTick1
promise then

// 第二次事件循环
setTimeout
process.nextTick2

process.nextTick 和 promise then在 setTimeout 前面输出,已经证明了macroTask和microTask的执行顺序。但是有一点必须要指出的是。上面的图容易给人一个错觉,就是主进程的代码执行之后,会先调用macroTask,再调用microTask,这样在第一个循环里一定是macroTask在前,microTask在后。

但是最终的实践证明:在第一个循环里,process.nextTick1和promise then这两个microTask是在setTimeout这个macroTask里之前输出的,这是因为Promises/A+规范规定主进程的代码也属于macroTask。

主进程这个macroTask(也就是main1、promise和main2)执行完了,自然会去执行process.nextTick1和promise then这两个microTask。这是第一个循环。之后的setTimeout和process.nextTick2属于第二个循环

别看上面那段代码好像特别绕,把原理弄清楚了,都一样 ~

requestAnimationFrame、Object.observe(已废弃) 和 MutationObserver这三个任务的运行机制大家可以从上面看到,不同的只是具体用法不同。重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTask和microTask之后,会进行一次Update the rendering,其中细节比较多,总的来说会进行一次UI的重新渲染。

3. 小结

总而言之,记住一次事件循环:先运行macroTask队列中的一个,然后运行microTask队列中的所有任务。接着开始下一次循环。

参考文献:

  • JavaScript Event Loop相关原理解析
  • 深入理解事件循环机制
  • JavaScript运行机制

以上就是深入分析JavaScript 事件循环(Event Loop)的详细内容,更多关于JavaScript 事件循环(Event Loop)的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JavaScript中的排序算法代码
Feb 22 Javascript
autoIMG 基于jquery的图片自适应插件代码
Mar 12 Javascript
js下获得客户端操作系统的函数代码(1:vista,2:windows7,3:2000,4:xp,5:2003,6:2008)
Oct 31 Javascript
关于jQuery $.isNumeric vs. $.isNaN vs. isNaN
Apr 15 Javascript
Javascript操作URL函数修改版
Nov 07 Javascript
javascript中数组的concat()方法使用介绍
Dec 18 Javascript
javascript中Date对象的getDay方法使用指南
Dec 22 Javascript
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
Oct 28 Javascript
js中通过getElementsByName访问name集合对象的方法
Oct 31 Javascript
js 开发之autocomplete="off"在chrom中失效的解决办法
Sep 28 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
Feb 23 Javascript
JS在if中的强制类型转换方式
Jul 15 Javascript
原生JS实现微信通讯录
Jun 18 #Javascript
vue+element-ui表格封装tag标签使用插槽
Jun 18 #Javascript
js实现ajax的用户简单登入功能
Jun 18 #Javascript
JS实现躲避粒子小游戏
Jun 18 #Javascript
html-webpack-plugin修改页面的title的方法
Jun 18 #Javascript
vue实现购物车结算功能
Jun 18 #Javascript
vue-cli4.x创建企业级项目的方法步骤
Jun 18 #Javascript
You might like
国产动画《伍六七》原声大碟大卖,啊哈娱乐引领音乐赋能IP的新尝试
2020/03/08 国漫
PHP mcrypt可逆加密算法分析
2011/07/19 PHP
PHP设计模式之结构模式的深入解析
2013/06/13 PHP
Zend Framework创建自己的动作助手详解
2016/03/05 PHP
PHP简单实现二维数组赋值与遍历功能示例
2017/10/19 PHP
基于swoole实现多人聊天室
2018/06/14 PHP
豆瓣网的jquery代码实例
2008/06/15 Javascript
用JavaScript实现单继承和多继承的简单方法
2009/03/29 Javascript
javascript replace()正则替换实现代码
2010/02/26 Javascript
JavaScript中OnLoad几种使用方法
2012/12/15 Javascript
如何将一个String和多个String值进行比较思路分析
2013/04/22 Javascript
jquery使用jquery.zclip插件复制对象的实例教程
2013/12/04 Javascript
javascript面向对象之定义成员方法实例分析
2015/01/13 Javascript
jQuery遍历DOM元素与节点方法详解
2016/04/14 Javascript
教你如何在Node.js中使用jQuery
2016/08/28 Javascript
通过jsonp获取json数据实现AJAX跨域请求
2017/01/22 Javascript
JS正则验证多个邮箱完整实例【邮箱用分号隔开】
2017/04/19 Javascript
Bootstrap里的文件分别代表什么意思及其引用方法
2017/05/01 Javascript
JavaScript Canvas实现验证码
2020/08/02 Javascript
koa源码中promise的解读
2018/11/13 Javascript
python查找目录下指定扩展名的文件实例
2015/04/01 Python
Python使用Matplotlib实现Logos设计代码
2017/12/25 Python
python实现的读取网页并分词功能示例
2019/10/29 Python
基于Django实现日志记录报错信息
2019/12/17 Python
使用Keras画神经网络准确性图教程
2020/06/15 Python
Python爬虫回测股票的实例讲解
2021/01/22 Python
英国第二大营养品供应商:Vitabiotics
2016/10/01 全球购物
美国睫毛、眉毛精华液领导品牌:RevitaLash Cosmetics
2018/03/26 全球购物
Dockers鞋官网:Dockers Shoes
2018/11/13 全球购物
Linux操作面试题
2015/02/11 面试题
投标授权委托书范文
2014/08/02 职场文书
党员教师四风问题对照检查材料
2014/09/26 职场文书
年底个人总结范文
2015/03/10 职场文书
旅行社计调工作总结
2015/08/12 职场文书
2016年“12.4”法制宣传日活动总结
2016/04/01 职场文书
准备去美国留学,那么大学申请文书应该怎么写?
2019/08/12 职场文书