博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript的Event Loop(浏览器)
阅读量:4709 次
发布时间:2019-06-10

本文共 3903 字,大约阅读时间需要 13 分钟。

春节的时候看到奇舞周刊发的关于Event Loop的文章https://mp.weixin.qq.com/s/KEl_IxMrJzI8wxbkKti5vg,看的也是迷迷糊糊。

昨天准备写一下几个Promise的小例子,发现理解起来还是要懂得Event Loop,所以又在网上找了几篇文章看了一下,发现各有各的说法,不过关于基础的部分都是一致的,

本着学习记录的心态,我这里简单地把我对Event Loop的理解说明一下,当然还是有很多不到位的地方,欢迎大家一起交流学习。

首先我们来看一张图片:(《Learning TypeScript》)

  • Heap 堆是一个内存存储空间,保存了正在被使用的对象,同时也保存在当前作用域已经不再会被用到的,但还没有被垃圾回收的frame(帧)。
  • Stack 栈,有文章称为调用栈或者执行栈,是一种后进先出的数据结构(只可以在栈顶进行操作)。主要存放Frame(帧)以及对应帧的一些基础类型变量和对象的指针。
  • Frame 帧,存放在Stack中(Stack中的小块),可以理解为一个个函数的执行环境。
  • Queue 队列,是一种先进先出的数据结构(在队列前面弹出数据,在队列尾部填充数据)。待执行信息(函数)的列表,当Stack为空时(Frame被执行完成),Queue的最前面的信息会被提取到Stack中进行处理。

比如有一个文件test.js

1 function A() { 2   console.log('A start') 3   Promise.resolve().then( 4     () => console.log('A 的 Promise then') 5   ) 6   console.log('A end') 7 } 8  9 function B() {10   console.log('B start')11   setTimeout(12     () => console.log('B 的 setTimeout'),13     014   )15   A();16   console.log('B end')17 }18 19 function C() {20   console.log('C start')21   B();22   console.log('C end')23 }24 C();

 

运行结果

  1. C start
  2. B start
  3. A start
  4. A end
  5. B end
  6. C end
  7. A 的 Promise then
  8. B 的 setTimeout
  • 在运行到调用C函数时,C函数的执行环境这个帧会被首先加入到栈中,这时它是栈顶,栈中只有它一个帧。队列此时是空的。
  • 执行C函数(打印出C start)后,发现C函数调用了B函数,这时就把B函数的执行环境推入栈顶。
  • 执行B函数(打印出B start,将setTimeout的回调函数放在任务队列中,这时任务队列有一个任务)后, 发现B函数调用了A函数,这时就把A函数的执行环境推入栈顶。
  • 执行A函数,打印出A start ,将Promise的then方法的回调函数放在任务队列中,这时任务队列就有了2个任务。继续执行A函数,直到打印出A end,
  • 这时A函数执行完毕,A函数的执行环境被弹出栈顶(这时栈中有2个帧 B和C),执行交还给B函数,B函数继续执行,直到打印出B end,
  • 这时B函数执行完毕,B函数的执行环境被弹出栈顶(这时栈中有1个帧 C),执行交还给C函数,C函数继续执行,直到打印出C end,
  • 这时C函数执行完毕,C函数的执行环境被弹出栈顶(这时栈中有0个帧,也就是说栈被清空了),
  • 栈被清空之后,系统会去任务队列中找任务(此时任务队列中有2个任务,一个是B函数添加进去的setTimeout的回调函数,一个是A函数添加进去的Promise的then方法的回调函数),

正常情况下,因为队列是先进先出的数据结构,B函数添加进去的setTimeout的回调函数比A函数添加进去的Promise的then方法的回调函数要早(其实也不早,不过这涉及到异步操作的问题,这里暂时这样理解,对于理解Event Loop没有影响)。

理所当然的以为会先打印出B 的 setTimeout,其实并没有,这里涉及到任务的分类,任务分为2类:

  1. MacroTask(宏任务)script全部代码、setTimeout、setInterval、setImmediate
  2. MicroTask(微任务)Promise、Observable

很显然,

B函数添加进去的setTimeout的回调函数进入宏任务队列中。A函数添加进去的Promise的then方法的回调函数进入微任务队列中。

需要明确的是,JS的机制是等到栈中为空时,会先去微任务队列寻找任务,根据先进先出的原则把微任务队列中的任务添加到栈中并执行,直到微任务队列为空时才会去宏任务队列中寻找任务。

所以出现了先打印A 的 Promise then 后打印出B 的 setTimeout

下面用图形解释一下(左边是代码执行到的位置,中间是堆栈信息,右边是console打印出来的信息)

 

 上面就是EventLoop最基本的原理,要记住的是

  1. 要等到栈清空会才会执行任务队列中的任务
  2. 根据1,得到同步任务即使花费的时间再长也比异步任务早执行
  3. Promise的回调比setTimeout的回调要早

其实上面的只是JS的主线程的逻辑,如果放在浏览器中执行,中间还会有JS线程移交执行权给UI线程渲染页面的过程,这个是另外的话题,之后我会单独写文章再介绍。

//  2019-02-10 23:08:16 添加例子

1 console.log('1'); 2 setTimeout(function() { 3     console.log('2'); 4     setTimeout(function() { 5       console.log('3'); 6     }) 7     new Promise(function(resolve) { 8         console.log('4'); 9         resolve();10     }).then(function() {11         console.log('5')12     })13 14 })15 new Promise(function(resolve) {16     console.log('6');17     resolve();18 }).then(function() {19     console.log('7')20 })21 22 setTimeout(function() {23     console.log('8');24     setTimeout(function() {25       console.log('9');26     })27     new Promise(function(resolve) {28         console.log('10');29         resolve();30     }).then(function() {31         console.log('11')32     })33 })34 console.log('12');

运行结果:

分析:

  • 1首先打印没话说
  • 6被打印是因为Promise的创建是立即执行的(是同步代码),Promise的then才是异步的
  • 12被打印是因为它是同步代码
  • 7被打印是因为then方法的回调是放在微任务队列中,它比宏任务队列要早执行,执行完7之后微任务队列为空,进入宏任务队列执行
  • 2被打印是因为2所在的setTimeout比8所在的setTimeout先将人宏任务队列,队列是先进先出的数据结构,所以2被先打印。
  • 4被打印的原因和6被打印的原因一样,打印之后任务完成,栈清空,会先去微任务队列中寻找任务。
  • 5被打印的原因是放在了微任务队列中,此时宏任务队列中有要打印8的setTimeout和要打印3的setTimeout,且要打印8的setTimeout的前面,
  • 8被打印的原因是微任务队列为空,去宏任务队列执行,要打印8的setTimeout在前面,所以打印了8
  • 10被打印的原因和6被打印的原因一样,打印之后任务完成,栈清空,会先去微任务队列中寻找任务。
  • 11被打印的原因是Promise的then的回调函数放在了微任务队列中,此时宏任务队列中有要打印3的setTimeout和要打印9的setTimeout,且要打印3的setTimeout的前面
  • 3被打印的原因是微任务队列为空,去宏任务队列执行,要打印3的setTimeout在前面,所以打印了3
  • 9被打印的原因是宏任务队列就还有它一个任务

作成:2019-02-10

修改:

  1. 2019-02-10 23:08:16 添加例子
  2. 2019-02-16 18:55:00 修改语法错误

参考: 

https://www.cnblogs.com/cangqinglang/p/8967268.html

https://mp.weixin.qq.com/s/KEl_IxMrJzI8wxbkKti5vg

转载于:https://www.cnblogs.com/wangtingnoblog/p/js_EventLoop.html

你可能感兴趣的文章
linux pip3本地安装
查看>>
关于iOS适配问题
查看>>
C语言博客作业--嵌套循环
查看>>
内部类 ( Inner Class )
查看>>
Linux 使用者 群组 权限
查看>>
【PAT】B1047 编程团体赛(20 分)
查看>>
iPad软件提交注意事项
查看>>
约束和异常处理
查看>>
css 布局
查看>>
RESTful风格化
查看>>
C# 多线程学习系列二
查看>>
如何将你的github仓库部署到github pages(转)
查看>>
几个重要的shell命令:diff patch tar find grep
查看>>
学习笔记
查看>>
JS ES6 -- let命令
查看>>
查找空座位问题
查看>>
几个简单规则改进你的SEO效果
查看>>
UVA10820 Send a Table
查看>>
主流css reset的讲解分析(转载)
查看>>
zookeeper 简介
查看>>