js 异步、栈、事件循环、任务队列

在开发中经常遇到js的异步问题,为了方便理解,记录下来,随时回顾。

  • 以下的所有代码都是在浏览器环境下运行

在浏览器中js的运行是依赖浏览器js引擎来解析的,并且是在一定的runtime(运行时)的环境被调用,被执行。由于js引擎是单线程的,所以在执行dom渲染,script引入的时候这些操作是同步的,js引擎会通过 Event Loop 的机制,按顺序把任务放入栈中执行

而在代码中产生的异步代码则是由 runtime 提供的,拥有和Js引擎互不干扰的线程

栈是一个后进先出的一种数据结构,执行起来效率比较高,往往堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针,在函数调用的时候,会产生函数的执行栈,也叫执行上下文,这个执行环境中存在着这个函数的私有作用域,上层作用域的指向,函数的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列函数被依次调用的时候,因为js是单线程的,同一时间只能执行一个函数,于是这些函数被排队在一个单独的地方。这个地方被称为执行栈。

当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕。

总的来说

  • 栈存放着一些基础类型变量以及对象的指针
  • 当代码执行的时候,同步代码按照执行顺序开始执行
  • 当代码执行的时候,碰到函数,引擎会在栈里产生这个函数执行栈,也叫执行上下文。
  • 当代码执行到函数的时候,会进入这个执行环境继续执行其中的代码,反复进行,全部执行完

任务队列

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个
  • 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • 微任务:process.nextTick (node.js中进程相关的对象), Promise, Object.observer, MutationObserver。

在浏览器的 Event Loop机制中,整个流程可以用张图来表示一下:

这张图中可以看到:

  • 微任务队列(micro tasks)只会有一个
  • 宏任务队列(macro tasks)可以有多个
  • click ajax 等回调方法都会进入到宏任务队列(macro tasks)中,当然也包括上面的

而在浏览器的Event Loop机制运行时,宏任务队列(macro tasks)和微任务队列(micro tasks)的关系,关于这点详见(关系

  • 宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面
  • 所有微任务也按顺序执行,且在以下场景会立即执行所有微任务
    • 每个回调之后且js执行栈中为空。
    • 每个宏任务结束后。

而在 NodeJs 的 Event Loop 遵循的是 libuv

这个库是node作者自己写的,内部实现了一整套的异步io机制(内部使用c++和js实现),使我们开发异步程序变得简单,因为这个原因导致了一些js解析和浏览器的会不一样。

NodeJs 的运行是这样的:

  • 初始化 Event Loop
  • 执行您的主代码。这里同样,遇到异步处理,就会分配给对应的队列。直到主代码执行完毕。
  • 执行主代码中出现的所有微任务:先执行完所有nextTick(),然后在执行其它所有微任务。
  • 开始 Event Loop

当每个阶段执行完毕后,都会执行所有微任务(先 nextTick,后其它),然后再进入下一个阶段。

最后我们来段代码彻底解析下两类任务队列在运行时的逻辑

console.log('1');

setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
}) setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})

结合上面讲的逻辑 可以分析一波得出最后答案是1,7,6,8,2,4,3,5,9,11,10,12

最新文章

  1. 四种比较简单的图像显著性区域特征提取方法原理及实现-----> AC/HC/LC/FT。
  2. SQL基础--ROWNUM伪列
  3. Mongodb学习笔记三(Mongodb索引操作及性能测试)
  4. Linux下开发常用 模拟 Http get和post请求
  5. 第六章 - 图像变换 - 图像拉伸、收缩、扭曲、旋转[1] - 仿射变换(cvWarpAffine)
  6. WPF Wonders: Transformations (and Robots!)
  7. MSM8974 fastboot烧写软件
  8. Linux中的终端、控制台、tty、pty等概念
  9. [转载]jQuery UI 使用
  10. 字符串[未AC](后缀自动机):HEOI 2016 str
  11. C#限制转换后的double小数点留1位
  12. iOS网络编程笔记——编写自己的网络客户端
  13. 一次关于js事件出发机制反常的解决记录
  14. XML解析之sax解析案例(二)使用sax解析把 xml文档封装成对象
  15. .net 解压缩 rar文件
  16. [转] GloVe公式推导
  17. python 的基础学习 第九天 文件的操作
  18. 步步为营-64-进程&线程
  19. vue-particles粒子动画效果
  20. NET-知识点:C#中Equals和==比较

热门文章

  1. Tomcat Script(python)
  2. 注册中心Eureka、Zookeeper、Consul的异同点
  3. Django学习路22_empty为空,forloop.counter 从1计数,.counter0 从0计数 .revcounter最后末尾数字是1,.revcounter0 倒序,末尾为 0
  4. PHP array_uintersect_assoc() 函数
  5. PDOStatement::bindValue
  6. win10搭建基于docker的odoo开发环境
  7. windows:shellcode生成框架和加载
  8. 关于c/c++中的二维数组与指针
  9. MapReduce之GroupingComparator分组(辅助排序、二次排序)
  10. NTFS 文件系统结构