深入分析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 相关文章推荐
jquery jqPlot API 中文使用教程(非常强大的图表工具)
Aug 15 Javascript
jquery鼠标滑过提示title具体实现代码
Aug 06 Javascript
js获取数组的最后一个元素
Apr 14 Javascript
ECMAScript5(ES5)中bind方法使用小结
May 07 Javascript
jQuery插件EnPlaceholder实现输入框提示文字
Jun 05 Javascript
Jquery插件easyUi实现表单验证示例
Dec 15 Javascript
js判断checkbox是否选中个数的方法(超简单)
Aug 19 Javascript
MUI 解决动态列表页图片懒加载再次加载不成功的bug问题
Apr 13 Javascript
JS基于对象的特性实现去除数组中重复项功能详解
Nov 17 Javascript
Vue中使用方法、计算属性或观察者的方法实例详解
Oct 31 Javascript
ES6扩展运算符和rest运算符用法实例分析
May 23 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
Sep 21 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
在Zeus Web Server中安装PHP语言支持
2006/10/09 PHP
PHP中防止SQL注入攻击和XSS攻击的两个简单方法
2010/04/15 PHP
jQuery 源码分析笔记
2011/05/25 PHP
phpQuery让php处理html代码像jQuery一样方便
2015/01/06 PHP
php图片水印添加、压缩、剪切的封装类实现
2020/04/18 PHP
Yii2中关联查询简单用法示例
2016/08/10 PHP
js parseInt("08")未指定进位制问题
2010/06/19 Javascript
javascript数组操作(创建、元素删除、数组的拷贝)
2014/04/07 Javascript
jQuery 浮动导航菜单适合购物商品类型的网站
2014/09/09 Javascript
javascript中call apply 的应用场景
2015/04/16 Javascript
js实现大转盘抽奖游戏实例
2015/06/24 Javascript
高效利用Angular中内置服务$http、$location等
2016/03/22 Javascript
Js 获取、判断浏览器版本信息的简单方法
2016/08/08 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
JavaScript实现的CRC32函数示例
2016/11/23 Javascript
BootStrap 导航条实例代码
2017/05/18 Javascript
基于ES6 Array.of的用法(实例讲解)
2017/09/05 Javascript
Vue组件库发布到npm详解
2018/02/17 Javascript
JavaScript类数组对象转换为数组对象的方法实例分析
2018/07/24 Javascript
微信小程序开发实现消息推送
2020/11/18 Javascript
基于vue+echarts 数据可视化大屏展示的方法示例
2020/03/09 Javascript
Vue双向绑定实现原理与方法详解
2020/05/07 Javascript
[03:01]完美世界DOTA2联赛PWL S2 集锦第二期
2020/12/03 DOTA
python中__slots__用法实例
2015/06/04 Python
Python实现读取邮箱中的邮件功能示例【含文本及附件】
2017/08/05 Python
Python实现批量修改图片格式和大小的方法【opencv库与PIL库】
2018/12/03 Python
通过cmd进入python的实例操作
2019/06/26 Python
TensorFlow2.0使用keras训练模型的实现
2021/02/20 Python
基于zepto的插件之移动端无缝向上滚动并上下触摸滑动实例代码
2016/12/20 HTML / CSS
时尚、社区、科技:SEVENSTORE
2019/04/26 全球购物
人力资源管理专业毕业生自我评价
2013/09/21 职场文书
体育比赛口号
2014/06/09 职场文书
2014院党委领导班子及其成员群众路线对照检查材料思想汇报
2014/10/04 职场文书
2015医德医风个人工作总结
2015/04/02 职场文书
班级元旦晚会开幕词
2016/03/04 职场文书
python脚本框架webpy模板赋值实现
2021/11/20 Python