浏览器运行过程中会同时面对多种任务,用户交互事件(鼠标、键盘)、网络请求、页面渲染等。而这些任务不能是无序的,必须有个先来后到,浏览器内部需要一套预定的逻辑来有序处理这些任务,因此浏览器事件循环诞生了,再次强调,是浏览器事件循环,不是javascript事件循环,js只是浏览器事件循环的参与者。

二、事件循环是什么

浏览器把任务区分成了 宏任务 和 微任务 或者叫 外部任务 和 内部任务 ,内部任务可以理解为js内部处理的任务,外部任务可以认为是浏览器处理的任务。

外部队列/宏任务队列(Task Queue)

也可以叫宏任务队列,浏览器中的外部事件源包含以下几种:

  • dom操作(页面渲染)
  • 用户交互(鼠标、键盘)
  • 网络请求(Ajax等)
  • History API操作(history.back、history.go...)
  • 定时器(setTimeout)

这些外部事件源可能很多,为了方便浏览器厂商优化,HTML标准中明确指出一个事件循环有一个或多个外部队列,而每一个外部事件源都有一个对应的外部队列。不同的时间源之间可以有不同的优先级(例如在网络时间和用户交互之间,浏览器可以优先处理鼠标行为,从而让用户感觉更加流畅)。

内部队列/微任务队列(Microtask Queue)

也可以叫微任务队列,指的就是javascript语言内部的事件队列,在HTML标准中,并没有明确规定这个队列的事件源,通常认为有以下几种:

  • Promise的成功(.then)与失败(.catch)
  • MutationObserver
  • Object.observe(已废弃)

以上三种除了第一个,其他两个可以认为没有,实际上我们js中能够使用的就只有promise。

事件循环模型

先来一张事件循环处理模型的截图:

可以看出,每一个事件循环,从外部任务队列中拿出一个来执行,执行完一个外部任务后立即执行内部任务队列中所有内部任务(清空),然后浏览器执行一次渲染,然后再次循环。

一段经典代码

了解了两种队列和事件循环的执行模型,下面来一段经典代码:

// 以下代码会得到什么样的输出结果?console.log('1');setTimeout(function() {   console.log('2');   Promise.resolve().then(function() {console.log('3');   });}, 0);Promise.resolve().then(function() {   console.log('4');}).then(function() {   console.log('5');});console.log('6');

答案是:164523

执行顺序如下:

  • 由于执行当前js代码这个任务是一个宏任务,因此首先输出的是"1",
  • 继续执行遇到setTimeou,由于setTimeout是一个外部事件源,它内部的代码会被push到TaskQueue中等待下一次事件循环再执行,
  • 当执行到promise的 then 或 catchd的时候会将他们按顺序追加到本轮事件循环的末尾,
  • 再继续往下执行输出6,宏任务完成后清空微任务队列中的任务,继而输出4、5
  • 如果有的话,执行渲染任务后,本次事件循环结束
  • 开始执行下一个宏任务,也就是第一个setTimeout中的代码块,输出2,然后将promise.then添加到本轮循环末尾
  • 清空微任务,输出3

三、浏览器与Node.js的事件循环差异

区别对比

对于两者的区别,来张瞟来的截图:

这个例子的代码如下:

setTimeout(()=>{   console.log('1');   Promise.resolve().then(function() {console.log('2'); });});setTimeout(()=>{   console.log('3');   Promise.resolve().then(function() {console.log('4');   });});

这段代码在浏览器和nodejs中的输出结果分别是什么呢?

通过前面对浏览事件循环的了解,你应该很容易得出在浏览器中的输出结果是: 1234

那在nodejs中的输出结果是什么呢?结果是在nodejs的 v11.x 之前输出1324。这之间的原因是浏览器有非常多的用户交互事件,为了用户体验更加流畅,必须均匀的处理宏任务和微任务,而在nodejs中由于并没有用户交互事件,为了保证异步事件能够被均等的执行,因此设计的初衷就是先清空宏任务队列再清空微任务队列。

不过你应该注意到,我上面只说了在 nodejs的 v11.x 之前输出1324,但是nodejs这个特性在社区经历了一波开发者的吐槽之后,node官方在 v11 这个版本紧急修复了这个问题。所以在 v11.x 以上版本执行以上代码会得到在浏览器中一样的结果。

setImmediate

先来张瞟来的截图:

我们再来一个例子:

setTimeout(()=>{console.log('1');Promise.resolve().then(() => console.log('2'));});setTimeout(()=>{  console.log('3');  Promise.resolve().then(() => console.log('4'));});setImmediate(() => {console.log('5');   Promise.resolve().then(() => console.log('6'));});setImmediate(() => {console.log('7');   Promise.resolve().then(() => console.log('8'));});

以上代码在nodejsV13.x中的执行结果是12345678,接下来我们把顺序调换一下,在第二个位置插入setImmediate

setTimeout(()=>{console.log('1');Promise.resolve().then(() => console.log('2'));});setImmediate(() => {console.log('3');   Promise.resolve().then(() => console.log('4'));});setTimeout(()=>{  console.log('5');  Promise.resolve().then(() => console.log('6'));});setImmediate(() => {console.log('7');   Promise.resolve().then(() => console.log('8'));});

执行结果有一定的概率是12347856,也有一定的概率是 12563478

为啥不同的顺序会得到不同的结果呢?这是由于setTImeout的精度问题导致的,到了这个级别的时间精度,代码执行的时间可能都会导致结果的不同。下面这张截图是nodejs官方文档对于事件循环顺序的展示:

其中timers阶段是用于执行setTimeout事件的,check阶段是用于执行setImmediate事件的。Nodejs官方这个所谓事件循环过程,其实只是完整的事件循环中Node.js的多个外部队列相互之间的优先级。setTimeout是由event loop检测系统时间是否到点然后向时间队列插入一个事件,然后调用事件的回调方法。而setImmediate是监控UI线程的调用栈,一旦调用栈为空则将回调压栈。

讲了这么多,其实对于上面setTimeout和setImmediate的对比结果还是有点模糊

推测:对于setImmediate的延时有时比setTimeout的要长,由于setImmediate要先监控调用栈,若调用栈为空才压栈,那么在压栈之前event loop已经将setTimeout事件的回调函数压栈了。

好了,以上是这次分享的所有内容,对于后面setTimeout和setImmedate的对比没有的出一个明确的结果,有兴趣的可以一起讨论。

5调用外部浏览器打开代码_浏览器事件循环相关推荐

  1. phpstorm安装_PHPstorm设置浏览器打开代码

    最近这几天带着班里同学安装phpstorm软件,有很多同学在打开代码时反应说在PHPstorm你无法使用右键或者浏览器小图标直接运行PHP的代码,今天就给大家一起来详细整理下如何配置在PHPstorm ...

  2. linux 火狐 清缓存,怎么清理新版火狐浏览器的缓存_浏览器指南

    怎么清理新版火狐浏览器的缓存_浏览器指南 发表时间:2020-09-29 来源:必杀器整理 软件安装:火狐浏览器 Mozilla Firefox,中文俗称"火狐"(正式缩写为Fx或 ...

  3. jq 自动打开浏览器_微信QQ跳转浏览器打开代码

    使用方法: 将代码全部复制 粘贴到 网站根目录下index.php文件的顶端 注意:不要覆盖了 index.php里面的原代码,原代码保留 使用说明: 手机QQ内打开,会自动跳转浏览器: 微信内打开, ...

  4. java如何关闭一个浏览器网页代码_使用java代码打开关闭浏览器(指定的浏览器或者计算机默认的浏览器)...

    package network.openURL; import java.io.IOException; import java.net.HttpURLConnection; import java. ...

  5. 【网站】 简单通用微信QQ跳转浏览器打开代码

    使用方法: 将代码全部复制 粘贴到 网站根目录下index.php文件的顶端 注意:不要覆盖了 index.php里面的原代码,原代码保留 使用说明: 手机QQ内打开,会自动跳转浏览器: 微信内打开, ...

  6. php微信转跳浏览器代码,通用微信QQ跳转浏览器打开代码

    演示效果如下 使用方法: 将代码全部复制 粘贴到 网站根目录下index.php文件的顶端 注意:不要覆盖了 index.php里面的原代码,原代码保留 使用说明: 手机QQ内打开,会自动跳转浏览器: ...

  7. 简单通用QQ/微信跳转浏览器打开代码

    使用方法: 将代码全部复制 粘贴到 网站根目录下index.php文件的顶端 注意:不要覆盖了 index.php里面的原代码,原代码保留(请尽快把样式以及图片本地化,以防失效) 使用说明: 手机QQ ...

  8. 简单通用QQ/微信跳转浏览器打开代码【小白教程简单易用】

    使用方法: 将代码全部复制 粘贴到 网站根目录下index.php文件的顶端 注意:不要覆盖了 index.php里面的原代码,原代码保留(请尽快把样式以及图片本地化,以防失效) 使用说明: 手机QQ ...

  9. vite2+vue3打包后浏览器打开跨域浏览器的错误

    浏览器打开html.index : Access to script at 'file:///D:/hehai/viteObj/dist/assets/index.559fd86e.js' from ...

最新文章

  1. redis学习笔记-持久化
  2. MyBatis DAO层开发——Mapper动态代理方式
  3. java泛型程序设计——定义简单泛型类+泛型方法
  4. XP-SP3 安装之后怎么禁止更新
  5. CSS默认可继承样式
  6. xtrabackup启动过程中出现的报错
  7. android之uniapp自定义基座
  8. An Introduction to Asynchronous Programming and Twisted (2)
  9. [转]Android调用so文件(C代码库)方法详解
  10. 初识OFDM(七):OFDM中的信道估计
  11. 怎么把png批量转换jpg格式?
  12. JPA中@Basic注解详解
  13. 三种POSS材料(乙烯基POSS、氨基POSS和苯基POSS)
  14. 使用 Ceph 作为 OpenStack 的统一存储解决方案
  15. 移动硬盘 无法读取 插入电脑没反应 无法识别 怎么办
  16. fm24c16c语言程序,单片机读写24C01~24C16程序
  17. SVN添加忽略文件规则
  18. HTC Desire HD(DHD G10) 刷机时MIUI卡在htc开机画面的解决方法
  19. 聚类评估算法-轮廓系数(Silhouette Coefficient )
  20. Win11找不到wt.exe如何解决?

热门文章

  1. 【渝粤教育】 国家开放大学2020年春季 1013金融统计分析 参考试题
  2. 遥控开关在云智能物联网领域:智能养殖高效、生态、安全!
  3. is array php,PHP 源码 — is_array 函数源码分析
  4. C语言实用算法系列之行指针
  5. mysql 授权用户_MySQL创建用户与授权
  6. android listview mapview,RelativeLayout和并列ListView/MapView
  7. linux18.2安装界面,Ubuntu 18.10下安装Grub Customizer 5.1.0配置grub2图形化界面
  8. oracle sql文字列函数,Oracle 数据库SQL中 decode()函数简介
  9. 锁相环锁相原理简洁版
  10. 启动时指定需要绑定的网卡_为什么小型汽油机在启动时需要拉风门,而汽车却不用?...