前端js中的事件循环eventloop机制详解


Posted in Javascript onMay 15, 2019

前言

我们知道 js 是单线程执行的,那么异步的代码 js 是怎么处理的呢?例如下面的代码是如何进行输出的:

console.log(1);
setTimeout(function() {
 console.log(2);
}, 0);
new Promise(function(resolve) {
 console.log(3);
 resolve(Date.now());
}).then(function() {
 console.log(4);
});
console.log(5);
setTimeout(function() {
 new Promise(function(resolve) {
  console.log(6);
  resolve(Date.now());
 }).then(function() {
  console.log(7);
 });
}, 0);

在不运行的情况可以先猜测下最终的输出,然后展开我们要说的内容。

1. 宏任务与微任务

依据我们多年编写 ajax 的经验:js 应该是按照语句先后顺序执行,在出现异步时,则发起异步请求后,接着往下执行,待异步结果返回后再接着执行。但他内部是怎样管理这些执行任务的呢?

在 js 中,任务分为宏任务(macrotask)和微任务(microtask),这两个任务分别维护一个队列,均采用先进先出的策略进行执行!同步执行的任务都在宏任务上执行。

宏任务主要有:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)。

微任务主要有:Promise.then、 MutationObserver、 process.nextTick(Node.js 环境)。

具体的操作步骤如下:

  1. 从宏任务的头部取出一个任务执行;
  2. 执行过程中若遇到微任务则将其添加到微任务的队列中;
  3. 宏任务执行完毕后,微任务的队列中是否存在任务,若存在,则挨个儿出去执行,直到执行完毕;
  4. GUI 渲染;
  5. 回到步骤 1,直到宏任务执行完毕;

这 4 步构成了一个事件的循环检测机制,即我们所称的eventloop。

回到我们上面说的代码:

console.log(1);
setTimeout(function() {
 console.log(2);
}, 0);
new Promise(function(resolve) {
 console.log(3);
 resolve(Date.now());
}).then(function() {
 console.log(4);
});
console.log(5);
setTimeout(function() {
 new Promise(function(resolve) {
  console.log(6);
  resolve(Date.now());
 }).then(function() {
  console.log(7);
 });
}, 0);

执行步骤如下:

  1. 执行 log(1),输出 1;
  2. 遇到 setTimeout,将回调的代码 log(2)添加到宏任务中等待执行;
  3. 执行 console.log(3),将 then 中的 log(4)添加到微任务中;
  4. 执行 log(5),输出 5;
  5. 遇到 setTimeout,将回调的代码 log(6, 7)添加到宏任务中;
  6. 宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,存在一个微任务 log(4)(在步骤 3 中添加的),执行输出 4;
  7. 取出下一个宏任务 log(2)执行,输出 2;
  8. 宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,不存在;
  9. 取出下一个宏任务执行,执行 log(6),将 then 中的 log(7)添加到微任务中;
  10. 宏任务执行完毕,存在一个微任务 log(7)(在步骤 9 中添加的),执行输出 7;

因此,最终的输出顺序为:1, 3, 5, 4, 2, 6, 7;

我们在Promise.then实现一个稍微耗时的操作,这个步骤看起来会更加地明显:

console.log(1);
var start = Date.now();
setTimeout(function() {
 console.log(2);
}, 0);
setTimeout(function() {
 console.log(4, Date.now() - start);
}, 400);
Promise.resolve().then(function() {
 var sum = function(a, b) {
  return Number(a) + Number(b);
 }
 var res = [];
 for(var i=0; i<5000000; i++) {
  var a = Math.floor(Math.random()*100);
  var b = Math.floor(Math.random()*200);
  res.push(sum(a, b));
 }
 res = res.sort();
 console.log(3);
})

Promise.then中,先生成一个500万随机数的数组,然后对这个数组进行排序。运行这段代码可以发现:马上会输出1,稍等一会儿才会输出3,然后再输出2。不论等待多长时间输出3,2一定会在3的后面输出。这也就印证了eventloop中的第3步操作,必须等所有的微任务执行完毕后,才开始下一个宏任务。

同时,这段代码的输出很有意思:

setTimeout(function() {
 console.log(4, Date.now() - start); // 4, 1380 电脑状态的不同,输出的时间差也不一样
}, 400);

本来要设定的是400ms后输出,但因为之前的任务耗时严重,导致之后的任务只能延迟往后排。也能说明,setTimeout和setInterval这种操作的延时是不准确的,这两个方法只能大概将任务400ms之后的宏任务中,但具体的执行时间,还是要看线程是否空闲。若前一个任务中有耗时的操作,或者有无限的微任务加入进来时,则会阻塞下一个任务的执行。

2. async-await

从上面的代码中也能看到 Promise.then 中的代码是属于微服务,那么 async-await 的代码怎么执行呢?比如下面的代码:

function A() {
  return Promise.resolve(Date.now());
}
async function B() {
  console.log(Math.random());
  let now = await A();
  console.log(now);
}
console.log(1);
B();
console.log(2);

其实,async-await 只是 Promise+generator 的一种语法糖而已。上面的代码我们改写为这样,可以更加清晰一点:

function B() {
  console.log(Math.random());
  A().then(function(now) {
    console.log(now);
  })
}
console.log(1);
B();
console.log(2);

这样我们就能明白输出的先后顺序了: 1, 0.4793526730678652(随机数), 2, 1557830834679(时间戳);

3. requestAnimationFrame

requestAnimationFrame也属于执行是异步执行的方法,但我任务该方法既不属于宏任务,也不属于微任务。按照MDN中的定义:

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

requestAnimationFrame是GUI渲染之前执行,但在微服务之后,不过requestAnimationFrame不一定会在当前帧必须执行,由浏览器根据当前的策略自行决定在哪一帧执行。

4. 总结

我们要记住最重要的两点:js是单线程和eventloop的循环机制。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
ExtJS4 组件化编程,动态加载,面向对象,Direct
May 12 Javascript
JS小功能(setInterval实现图片效果显示时间)实例代码
Nov 28 Javascript
JS如何将数字类型转化为没3个一个逗号的金钱格式
Jan 27 Javascript
document.addEventListener使用介绍
Mar 07 Javascript
JavaScript实现时间倒计时跳转(推荐)
Jun 28 Javascript
onmouseover事件和onmouseout事件全面理解
Aug 15 Javascript
vuejs2.0运用原生js实现简单的拖拽元素功能示例
Feb 24 Javascript
Vue中引入样式文件的方法
Aug 18 Javascript
vue项目每30秒刷新1次接口的实现方法
Dec 04 Javascript
使用VueRouter的addRoutes方法实现动态添加用户的权限路由
Jun 03 Javascript
JavaScript享元模式原理与用法实例详解
Mar 09 Javascript
JS addEventListener()和attachEvent()方法实现注册事件
Jan 11 Javascript
mpvue小程序循环动画开启暂停的实现方法
May 15 #Javascript
微信小程序的tab选项卡的实现效果
May 15 #Javascript
少女风vue组件库的制作全过程
May 15 #Javascript
vue两组件间值传递 $router.push实现方法
May 15 #Javascript
JavaScript+HTML5 canvas实现放大镜效果完整示例
May 15 #Javascript
详解微信UnionID作用
May 15 #Javascript
小程序:授权、登录、session_key、unionId的详解
May 15 #Javascript
You might like
centos+php+coreseek+sphinx+mysql之一coreseek安装篇
2016/10/25 PHP
Jquery 基础学习笔记之文档处理
2009/05/29 Javascript
IE中radio 或checkbox的checked属性初始状态下不能选中显示问题
2009/07/25 Javascript
Jquery Ajax学习实例4 向WebService发出请求,返回实体对象的异步调用
2010/03/16 Javascript
用js来解决ajax读取页面乱码
2010/11/28 Javascript
jQuery向上遍历DOM树之parents(),parent(),closest()之间的区别
2013/12/02 Javascript
jquery对单选框,多选框,文本框等常见操作小结
2014/01/08 Javascript
自定义jquery模态窗口插件无法在顶层窗口显示问题
2014/05/29 Javascript
scrollWidth,clientWidth,offsetWidth的区别
2015/01/13 Javascript
AngularJS 模型详细介绍及实例代码
2016/07/27 Javascript
AngularJS使用ng-repeat指令实现下拉框
2016/08/23 Javascript
浅谈vue 多个变量同时赋相同值互相影响
2020/08/05 Javascript
Python中zip()函数用法实例教程
2014/07/31 Python
Python map和reduce函数用法示例
2015/02/26 Python
对Python中plt的画图函数详解
2018/11/07 Python
python爬取淘宝商品销量信息
2018/11/16 Python
Opencv+Python 色彩通道拆分及合并的示例
2018/12/08 Python
对Python中实现两个数的值交换的集中方法详解
2019/01/11 Python
Python序列化与反序列化pickle用法实例
2019/11/11 Python
解决python web项目意外关闭,但占用端口的问题
2019/12/17 Python
使用Python+selenium实现第一个自动化测试脚本
2020/03/17 Python
pandas读取csv文件提示不存在的解决方法及原因分析
2020/04/21 Python
python 制作python包,封装成可用模块教程
2020/07/13 Python
Django解决frame拒绝问题的方法
2020/12/18 Python
Biblibili视频投稿接口分析并以Python实现自动投稿功能
2021/02/05 Python
英国男女奢华内衣和泳装购物网站:Figleaves
2017/01/28 全球购物
便携式太阳能系统的创新者:GOAL ZERO
2018/02/04 全球购物
乌克兰最大的家用电器和电子产品连锁店:Eldorado
2019/10/02 全球购物
安德玛菲律宾官网:Under Armour菲律宾
2020/07/28 全球购物
八年级生物教学反思
2014/01/22 职场文书
教师党员承诺书
2014/03/25 职场文书
租房协议书
2014/04/10 职场文书
摄影专业毕业生求职信
2014/08/05 职场文书
课前一分钟演讲稿
2014/08/26 职场文书
python实现自动清理文件夹旧文件
2021/05/10 Python
idea编译器vue缩进报错问题场景分析
2021/07/04 Vue.js