视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
浏览器与NodeJS的EventLoop异同以及部分机制 浏览器互相跳转并传递参数(附代码) 新手必看的js实现异步方法 新手入门如何学习vue 新手必看的Ajax技术组成与核心原理分析 新手必学的Ajax总结 新手如何通过Vue.js+Node.js打造个人博客 浏览器的多线程机制详解 浏览器访问路径不提示下载而显示新页面应该如何解决 浏览器html代码快速预览小工具 浏览器文件分段断点上传 新手学习vue详解 浏览器不兼容我写的代码怎么办 浏览器变动时进行自适应代码分享 浏览器调试动态js脚本的方法图解教程 浏览器记住密码后input黄色背景处理方法 华硕笔记本怎么开启uefi引导 美的冰箱冷藏室为什么会结冰 怎么看拼多多卖家直播 格力空调故障代码e8什么原因 浏览器缓存机制的深入解析(图文) 浏览器的进程与线程的介绍 浏览器输入url到发起http请求的具体过程介绍 浏览器与Node的事件循环(EventLoop)之间的区别总结 浏览器解析渲染HTML文档的过程详解(图文) 浏览器缓存策略的详细介绍(图文) 脚本是什么 alert是什么意思 新手学习PHP的一些基础知识分享 新手配置 PHP 调试环境(IIS+PHP+MYSQL) 浏览器兼容解决FF/IE6/IE7背景专用CSSHACK_经验交流 史上最强大的40多个纯CSS绘制的图形 浏览器Firefox与IE在CSS样式表中的差异 浏览器默认样式及css初始化 新手必看的css命名规则 浏览器CSSHack收集 浏览器实现移动端高性能css3动画 沙盘Sandboxie 史上最全前端面试题(含答案)-1565783227 浏览器兼容性常见-地球小子
浏览器事件循环的深入了解(代码示例)
2020-11-27 19:29:14 责编:小采
文档

本篇文章给大家带来的内容是关于浏览器事件循环的深入了解(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

浏览器的事件循环,前端再熟悉不过了,每天都会接触的东西。但我以前一直都是死记硬背:事件任务队列分为macrotask和microtask,浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行...,这样一直循环下去。但是对于下面的代码,我一直懵逼,setTimeout属于macrotask,按照上面的规则,setTimeout应该先被取出来执行啊,但是我却被执行结果打脸了。

<script>
 setTimeout(() => {
 console.log(1)
 }, 0)
 new Promise((resolve) => {
 console.log(2)
 resolve()
 }).then(() => {
 console.log(3)
 })
 // 我曾经的预期是:2 1 3
 // 实际
输出:2 3 1 </script>

经过再仔细看别人对任务队列的介绍,才知道,同步执行的js代码其实就算一个macrotask(准确说是每一个script标签内的代码都是一个macrotask),所以上面的规则中说的 先取出一个macrotask执行 是没有问题的。
网上很多文章都是像上面这样解释的,我也一直认为这是HTML对事件循环的规范,我们记着就是。直到最近看了李银城大佬的文章(见文末的参考链接),我才恍然大悟,之前看的文章都没有明确地从浏览器的多线程模型这个角度分析,所以让我们觉得浏览器的事件循环是基于上述的约定,但其实这是浏览器的多线程模型导致的结果。

macrotask的本质

macrotask本质上是浏览器多个线程之间通信的一个消息队列
在chrome里,每个页面都对应一个进程,该进程又有多个线程,比如js线程、渲染线程、io线程、网络线程、定时器线程等,这些线程之间的通信,是通过向对方的任务队列中添加一个任务(PostTask)来实现的。

浏览器的各种线程都是常驻线程,它们运行在一个for死循环里面,每个线程都有属于自己的若干任务队列,线程自己或者其它线程都可能通过PostTask向这些任务队列添加任务,这些线程会不断地从自己的任务队列中取出任务执行,或者是处于睡眠状态直到设定的时间或者是有人PostTask的时候把它们唤醒。

可以简单地理解为,浏览器的各个线程都在不停地从自己的任务队列中取出任务,执行,再取出任务,再执行,这样无限循环下去。

以下面的代码为例:

<script>
 console.log(1)
 setTimeout(() => {
 console.log(2)
 }, 1000)
 console.log(3)
</script>
  1. 首先,script标签中的代码作为一个任务放入js线程的任务队列,js线程被唤醒,然后取出该任务执行

  2. 首先执行console.log(1),然后执行setTimeout,向定时器线程添加一个任务,接着执行console.log(3),这时js线程的任务队列为空,js线程进入休眠

  3. 大约1000ms后,定时器线程向js线程的任务队列添加定时任务(定时器的回调),js线程又被唤醒,执行定时回调函数,最后执行console.log(2)。

可以看到,所谓的macrotask并不是浏览器定义了哪些任务是macrotask,浏览器各个线程只是忠实地循环自己的任务队列,不停地执行其中的任务而已。

microtask

比起macrotask是浏览器的多线程模型造成的“假象”,microtask是确实存在的一个队列,microtask是属于当前线程的,而不是其他线程PostTask过来的任务,只是延迟执行了而已(准确地说是放到了当前执行的同步代码之后执行),比如Promise.then、MutationObserver都属于这种情况。

以下面的代码为例:

<script>
 new Promise((resolve) => {
 resolve()
 console.log(1)
 setTimeout(() => {
  console.log(2)
 },0)
 }).then(() => {
 console.log(3)
 })
 // 
输出:1 3 2 </script>
  1. 首先,script标签中的代码作为一个任务放入js线程的任务队列,js线程被唤醒,然后取出该任务执行

  2. 然后执行new Promise以及Promise中的resolve,resolve后,promise的then的回调函数会作为需要延迟执行的任务,放到当前执行的所有同步代码之后

  3. 接着执行setTimeout,向定时器线程添加一个任务

  4. 此时同步代码执行完毕,接着执行被延迟执行的任务,也就是promise的then的回调函数,即执行console.log(3)

  5. 最后,js线程的任务队列为空,js线程进入休眠,大约1000ms后,定时器线程向js线程的任务队列添加定时任务(定时器的回调),js线程又被唤醒,执行定时回调函数,即console.log(2)。

总结

通过上面的分析,可以看到,文章开头提到的规则:浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行...,并没有说错,但这只是浏览器执行机制造成的现象,而不是说浏览器按照这样的规则去执行的代码。
这篇文章中的所有干货都来自李银成大佬的文章,我只是按照自己的理解,做了简化描述,方便大家理解,也加深自己的印象。
最后,看了这篇文章,大家能够基于浏览器的运行机制,分析出下面代码的执行结果了吗(ps:不要用死记硬背的规则去分析哟)

console.log('start')

const interval = setInterval(() => { 
 console.log('setInterval')
}, 0)

setTimeout(() => { 
 console.log('setTimeout 1')
 Promise.resolve()
 .then(() => {
 console.log('promise 3')
 })
 .then(() => {
 console.log('promise 4')
 })
 .then(() => {
 setTimeout(() => {
  console.log('setTimeout 2')
  Promise.resolve()
  .then(() => {
  console.log('promise 5')
  })
  .then(() => {
  console.log('promise 6')
  })
  .then(() => {
  clearInterval(interval)
  })
 }, 0)
 })
}, 0)

Promise.resolve()
 .then(() => { 
 console.log('promise 1')
 })
 .then(() => {
 console.log('promise 2')
 })
// 执行结果
/* start
 promise 1
 promise 2
 setInterval
 setTimeout 1
 promise 3
 promise 4
 setInterval
 setTimeout 2
 promise 5
 promise 6
*/

下载本文
显示全文
专题苹果笔记本截屏的快捷键是什么苹果笔记本截屏的快捷键是什么专题iphone照片永久删除怎么恢复iphone照片永久删除怎么恢复专题废手机怎样提炼黄金废手机怎样提炼黄金专题手机卡没信号怎么回事手机卡没信号怎么回事专题电脑启动不了反复启动电脑启动不了反复启动专题iphone视频怎么转音频iphone视频怎么转音频专题qq个人文件夹中的文件被占用暂时无法登录qq个人文件夹中的文件被占用暂时无法登录专题word表格每页固定表头word表格每页固定表头专题注册微信怎么跳过辅助注册微信怎么跳过辅助专题微信收藏占用手机存储空间吗微信收藏占用手机存储空间吗专题无手机号怎么注册qq无手机号怎么注册qq专题橡胶刺鼻气味如何去除橡胶刺鼻气味如何去除专题家里的蟑螂是怎么来的家里的蟑螂是怎么来的专题怎么赶走黄鼠狼妙招怎么赶走黄鼠狼妙招专题蟑螂怎么来的蟑螂怎么来的专题相互宝怎么取消相互宝怎么取消专题相互保怎么退出相互保怎么退出专题雪是怎么形成的雪是怎么形成的专题蟑螂是怎么进入家里的蟑螂是怎么进入家里的专题洁厕灵蓝泡泡怎么用洁厕灵蓝泡泡怎么用专题如何判断面粉变质如何判断面粉变质专题房间里有老鼠用什么方法可以解决房间里有老鼠用什么方法可以解决专题家庭野生菌冷冻保存技巧家庭野生菌冷冻保存技巧专题竹荪煮多长时间熟竹荪煮多长时间熟专题老鼠胶怎么洗掉老鼠胶怎么洗掉专题怎么杀蟑螂怎么杀蟑螂专题航班号怎么看航班号怎么看专题花雕酒怎么喝花雕酒怎么喝专题板栗壳怎么好剥板栗壳怎么好剥专题烤红薯没有锡纸怎么办烤红薯没有锡纸怎么办专题js文件分析专题js 版本控制专题html 组件库专题自定义js组件专题信息隐藏代码实现专题js 消息提示插件专题js 观察者专题vue.js源码全方位深入解析专题观察者模式js专题js观察者模式专题观察者模式 js专题js 观察者模式专题uniapp 调用组件专题子组件调用父组件专题父组件调用子组件专题小程序点击事件传参数专题调用父组件方法专题代码静态解析专题页面加载前执行js代码专题小程序引用插件专题