当前位置: 首页 > news >正文

彻底搞懂浏览器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 是如何工作的?

以一个示例来说明:

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)。

5. 参考文章:

  1. https://segmentfault.com/a/11...
  2. https://segmentfault.com/a/11...
  3. https://segmentfault.com/a/11...
  4. http://www.ruanyifeng.com/blo...

相关文章:

  • jQuery插件 -- Cookie插件jquery.cookie.js(转)
  • Hbulider MUI
  • 快速有效的增加App真实评论的简单方法!
  • Spring Cloud 入门教程5、服务容错监控:Hystrix Dashboard
  • rabbitmq单机多实例集群搭建
  • j2EE监听器-listener
  • jQuery EasyUI使用教程之使用虚拟滚动视图显示海量数据
  • DevOps团队结构类型汇总:总有一款适合你
  • CSS学习笔记(五)背景
  • 独家!支付宝小程序技术架构全解析
  • linux关闭ssh连接
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [Redis]Redis的数据类型
  • Leetcode题目:Balanced Binary Tree
  • 我是如何设计 Upload 上传组件的
  • AngularJS指令开发(1)——参数详解
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • rabbitmq延迟消息示例
  • v-if和v-for连用出现的问题
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 离散点最小(凸)包围边界查找
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 前嗅ForeSpider采集配置界面介绍
  • 算法-插入排序
  • 小而合理的前端理论:rscss和rsjs
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​一些不规范的GTID使用场景
  • #《AI中文版》V3 第 1 章 概述
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (安卓)跳转应用市场APP详情页的方式
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (剑指Offer)面试题34:丑数
  • .cfg\.dat\.mak(持续补充)
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET Framework 4.6.2改进了WPF和安全性
  • .Net IOC框架入门之一 Unity
  • .net 使用ajax控件后如何调用前端脚本
  • .net操作Excel出错解决
  • .Net中间语言BeforeFieldInit
  • @RequestBody的使用
  • [145] 二叉树的后序遍历 js
  • [BZOJ3223]文艺平衡树
  • [Codeforces] number theory (R1600) Part.11
  • [CSDN首发]鱿鱼游戏的具体玩法详细介绍
  • [EFI]Dell Inspiron 15 5567 电脑 Hackintosh 黑苹果efi引导文件
  • [flask]http请求//获取请求头信息+客户端信息
  • [Git 1]基本操作与协同开发
  • [HTML]Web前端开发技术28(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [IE技巧] 使IE8以单进程的模式运行
  • [LeetCode]-283. 移动零-1089. 复写零
  • [python]基本输出输入函数
  • [Spark][Hive]Hive的命令行客户端启动: