为什么会写这篇博文呢?

前段时间,和头条的小伙伴聊天问头条面试前端会问哪些问题,他称如果是他面试的话,event-loop肯定是要问的。那天聊了蛮多,event-loop算是给我留下了很深的印象。原因很简单,因为之前我从未深入了解过,如果是面试的时候,我遇到了这个问题,估计回答得肯定不如人意。

因此,最近我阅读了一些相关的文章,并细细梳理了一番,输出了本篇博文,希望能帮助大家搞懂浏览器的event-loop。后续会继续补充node中的event-loop。

1. 预备知识

JavaScript的运行机制:

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

概括即是: 调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作

一个事件循环中有一个或者是多个任务队列

JavaScript中有两种异步任务:

  1. 宏任务: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering

  2. 微任务: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;

2. 事件循环(event-loop)是什么?

主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。

详细说明:

  1. 选择当前要执行的宏任务队列,选择一个最先进入任务队列的宏任务,如果没有宏任务可以选择,则会跳转至microtask的执行步骤。
  2. 将事件循环的当前运行宏任务设置为已选择的宏任务。
  3. 运行宏任务。
  4. 将事件循环的当前运行任务设置为null。
  5. 将运行完的宏任务从宏任务队列中移除。
  6. microtasks步骤:进入microtask检查点。
  7. 更新界面渲染。
  8. 返回第一步。

执行进入microtask检查的的具体步骤如下:

  1. 设置进入microtask检查点的标志为true。
  2. 当事件循环的微任务队列不为空时:选择一个最先进入microtask队列的microtask;设置事件循环的当前运行任务为已选择的microtask;运行microtask;设置事件循环的当前运行任务为null;将运行结束的microtask从microtask队列中移除。
  3. 对于相应事件循环的每个环境设置对象(environment settings object),通知它们哪些promise为rejected。
  4. 清理indexedDB的事务。
  5. 设置进入microtask检查点的标志为false。

需要注意的是:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。

图示:

3. Event-loop 是如何工作的?

先看一个简单的示例:

setTimeout(()=>{console.log("setTimeout1");Promise.resolve().then(data => {console.log(222);});
});
setTimeout(()=>{console.log("setTimeout2");
});
Promise.resolve().then(data=>{console.log(111);
});
复制代码

思考一下, 运行结果是什么?

运行结果为:

111
setTimeout1
222
setTimeout2
复制代码

我们来看一下为什么?

我们来详细说明一下, JS引擎是如何执行这段代码的:

  1. 主线程上没有需要执行的代码
  2. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。
  3. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。
  4. 首先检查微任务队列, 即 microtask队列,发现此队列不为空,执行第一个promise的then回调,输出 '111'。
  5. 此时microtask队列为空,进入下一个事件循环, 检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 'setTimeout1',检查microtask 队列,发现队列不为空,执行promise的then回调,输出'222',microtask队列为空,进入下一个事件循环。
  6. 检查宏任务队列,发现有 setTimeout的回调函数, 立即执行回调函数输出'setTimeout2'。

再思考一下下面代码的执行顺序:

console.log('script start');setTimeout(function () {console.log('setTimeout---0');
}, 0);setTimeout(function () {console.log('setTimeout---200');setTimeout(function () {console.log('inner-setTimeout---0');});Promise.resolve().then(function () {console.log('promise5');});
}, 200);Promise.resolve().then(function () {console.log('promise1');
}).then(function () {console.log('promise2');
});
Promise.resolve().then(function () {console.log('promise3');
});
console.log('script end');
复制代码

思考一下, 运行结果是什么?

运行结果为:

script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0
复制代码

那么为什么?

我们来详细说明一下, JS引擎是如何执行这段代码的:

  1. 首先顺序执行完主进程上的同步任务,第一句和最后一句的console.log
  2. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中(这个任务在下一次的事件循环中执行)。
  3. 接着遇到setTimeout 200,它的作用是在 200ms 后将回调函数放到宏任务队列中(这个任务在再下一次的事件循环中执行)。
  4. 同步任务执行完之后,首先检查微任务队列, 即 microtask队列,发现此队列不为空,执行第一个promise的then回调,输出 'promise1',然后执行第二个promise的then回调,输出'promise3',由于第一个promise的.then()的返回依然是promise,所以第二个.then()会放到microtask队列继续执行,输出 'promise2';
  5. 此时microtask队列为空,进入下一个事件循环, 检查宏任务队列,发现有 setTimeout的回调函数,立即执行回调函数输出 'setTimeout---0',检查microtask 队列,队列为空,进入下一次事件循环.
  6. 检查宏任务队列,发现有 setTimeout的回调函数, 立即执行回调函数输出'setTimeout---200'.
  7. 接着遇到setTimeout 0,它的作用是在 0ms 后将回调函数放到宏任务队列中,检查微任务队列,即 microtask 队列,发现此队列不为空,执行promise的then回调,输出'promise5'。
  8. 此时microtask队列为空,进入下一个事件循环,检查宏任务队列,发现有 setTimeout 的回调函数,立即执行回调函数输出,输出'inner-setTimeout---0'。代码执行结束.

4. 为什么会需要event-loop?

因为 JavaScript 是单线程的。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。

最后有一点需要注意的是:本文介绍的是浏览器的Event-loop,因此在测试验证时,一定要使用浏览器环境进行测试验证,如果使用了node环境,那么结果不一定是如上所说。

5. 参考文章:

  1. segmentfault.com/a/119000001…
  2. segmentfault.com/a/119000001…
  3. segmentfault.com/a/119000001…
  4. www.ruanyifeng.com/blog/2014/1…

最后,如果您觉得本篇博文给了您一点帮助或者启发,请帮忙点个Star吧~ github.com/YvetteLau/B…

关注小姐姐的公众号,和小姐姐一起学前端。

彻底搞懂浏览器Event-loop相关推荐

  1. AEJoy —— 彻底搞懂 AE 各种 loop* 表达式【二】

    在前一篇文章<AEJoy -- 彻底搞懂 AE 各种 loop* 表达式[一]>,我们讲解了 loopIn/loopOut 在不同 numKeyframes 的情况下的一些区别,但是 ty ...

  2. 一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别

    先总结下区别:event.clientX.event.clientY鼠标相对于浏览器窗口可视区域的X,Y坐标(窗口坐标),可视区域不包括工具栏和滚动条.IE事件和标准事件都定义了这2个属性event. ...

  3. 6张图让你搞懂浏览器渲染网页过程

    我的想法:如果我要构建快速可靠的网站,需要真正了解浏览器渲染网页的每个步骤机制,这样就可以在开发过程中对每个步骤进行优化. 这篇文章是我在较高水平上对端到端过程的学习总结. 好了,废话不多说,我们开始 ...

  4. 彻底搞懂浏览器Event-loop 1

    1. 预备知识 JavaScript的运行机制: (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack). (2)主线程之外,还存在"任务队列& ...

  5. 一文搞懂浏览器同源策略

    摘要:同源策略就是指必须在同一个协议,域名,端口号下,而且三者必须一致的. 本文会从以下几个方面讲述同源策略: 第一点 what:什么是同源策略 第二点 why:为什么需要同源策略 第三点 how:如 ...

  6. 一文搞懂浏览器缓存机制

    最近在项目中遇到了 IE浏览器因缓存问题未能成功向后端发送 GET类型请求 的bug,然后顺藤摸瓜顺便看了看缓存的知识,觉得有必要总结跟大家分享一下. 在前端开发中,性能一直都是被大家所重视的一点,然 ...

  7. 一文让你彻底搞懂浏览器的渲染流程

    hello,大家好.上次为大家介绍了重排和重绘的一些内容,其中涉及到的浏览器渲染流程自己就没有在上篇博文详细介绍了.今天我们就来好好唠唠浏览器的整个渲染流程. 首先,我们知道,一个页面通常由三个部分组 ...

  8. AEJoy —— 彻底搞懂 AE 各种 loop* 表达式【一】

    前言 通过阅读本文,您将收获以下知识点 loopIn(type, numKeyframes) loopOut(type, numKeyframes) loopInDuration(type, dura ...

  9. js异步等待完成后再进行下一步操作_彻底搞懂JS事件中的循环机制 Event Loop

    我们都知道JavaScript是单线程语言,就是因为单线程的特性,就不得不提js中的同步和异步 一.同步和异步 所谓单线程,无非就是同步队列和异步队列,js代码是自上向下执行的,在主线程中立即执行的就 ...

最新文章

  1. go语言学习-iota
  2. 焦虑的 BAT、不安的编程语言,揭秘程序员技术圈生存现状!
  3. 小学计算机笔记,小学信息技术教师读书笔记
  4. 基于JAVA+SpringMVC+MYSQL的网上选课系统
  5. ScheduledExecutorService线程调度的使用
  6. RGB数据保存为BMP图片
  7. FreeRTOS的HOOK,以及(23)FreeRTOS 空闲任务分析
  8. vue 文件转base64方法 base64转blob路径方法 a链接下载文件 form表单下载文件
  9. 微信小程序 异步加载f2图表
  10. 靶机渗透练习06-driftingblues6 (利用脏牛提权)
  11. ArrayList和Linked的区别
  12. 分析APP的安装流程 API29
  13. 【获奖案例巡展】信创先锋之星——云上贵州信创工程中心大数据中台
  14. 【论文笔记 | TGRS 2021】多内容互补网络:MCCNet
  15. DEDECMS后台文章发布出错解决方案
  16. 百面机器学习2---模型评估
  17. atom写css,Atom编写Markdown
  18. Epson机器人原点与左右手矫正说明
  19. 2019联想小新pro13.3 Intel i7 10710U+MX250 liinux双系统安装及美化修改(ubuntu19.10 / ubuntu18.04.4 / ubuntu20.04)
  20. 虚拟机u盾怎么使用_虚拟机用U盾支付安全吗

热门文章

  1. 面试者让金山负责webgame的高管崩溃了!
  2. Mybatis之批量更新操作
  3. Latex 实时排版工具
  4. 2015百度之星 列变位法解密
  5. Kali Linux 64位安装WPS
  6. 诺康得NKD完成500万天使轮融资,专注于糖化学细胞治疗...
  7. tar打包及打包并压缩
  8. (原)linux下caffe模型转tensorflow模型
  9. 与Susan Fowler探讨生产就绪微服务之问答
  10. 初识Memcached