📖
blog
  • README
  • JavaScript
    • 元素的宽高位置信息梳理
    • dom-align 源码浅析
    • Event Loop
    • 函数实参为对象时的陷阱
    • export 与 utils 方法书写规范
    • 手写 Promise 及相关代码理解
    • call,apply,bind 等函数的模拟实现
    • JavaScript继承
    • JavaScript 数据类型与类型判断
    • for..of 和 for..in 的区别
    • 写给自己看的 next 函数
    • JS 可选链与双问号
    • mouseenter 与 mouseover 事件的区别
    • Immutable相关知识
  • CSS
    • 不简单的 z-index
    • 两列布局,三列布局
    • CSS 居中方案整理
    • CSS 像素,设备像素,2倍图梳理
    • iconfont 的使用
  • Node JS
    • 实现简易的 express
  • React 核心知识点整理
    • 高阶组件
    • React 事件处理
    • React Hooks
    • React Context
  • React 状态管理
    • Redux 基础概念
    • Redux 中间件和异步操作
    • Redux Saga
    • Redux 只能有一个 store 对象嘛
  • React 开发实践
    • Ant Design Menu 组件的使用与深入
    • 讲讲吸顶效果与 react sticky
    • 基于 express,搭建 react 的开发环境
    • 通过 antd input 组件分析受控与非受控组件
    • DebounceClick 组件
    • react component Align 组件分析
    • React Portal 之事件冒泡
    • React Transition Group 源码浅析
    • React.cloneElement 父组件向子组件注入 props
    • 一次 Align 组件的问题记录
    • 如何知道子组件的类型
    • React Router 源码简单分析
    • React Redux 源码简单分析
  • Vue.js
    • Vue.js 概览
    • scoped 样式中的 deep
  • TypeScript 语法
    • 基础类型
    • 变量声明
    • 接口
    • 类
    • 函数
    • 泛型
    • 枚举
    • 类型推论
    • 类型兼容性
    • 高级类型
    • Symbol
    • 迭代器和生成器
    • 模块
    • 命名空间
    • JSX
  • 玩转 webpack
    • 第一章: webpack 与构建发展简史
    • 第二章:webpack基础用法
    • 第三章:webpack进阶用法
    • 第四章:编写可维护的 webpack 构建配置
    • 第五章:webpack构建速度和体积优化策略
    • 第六章:通过源代码掌握webpack打包原理
    • 第七章:编写Loader和插件
  • webpack 实践
    • 如何配置 output.library
  • 测试
    • 初识代码测试
    • Jest 中 如何测试 setTimeout
    • Jest Enzyme React 测试实践记录
  • WEB 开发,过往工作沉淀
    • Web安全(DVWA)
    • 内存泄露与事件移除的必要性
    • url to pdf api 与 服务部署踩坑记录
    • 前端调试指南
    • Markdown 转 email
    • github travis ci 自动部署
    • 浏览器缓存知识梳理
    • WEB 系统登录相关知识梳理
    • 将-Axios-请求参数和返回值进行格式化
    • source-map与源码调试
    • HTTPS
    • 使用 rollup 打造自己的 npm 包 (全流程)
    • father-build 是如何工作的
  • 书籍
    • 图解 HTTP 协议
    • 编写可维护的 JavaScript
    • 鸟哥的 Linux 私房菜
    • JavaScript Promise迷你书
  • Linux
    • vimtutor
    • CURL 使用指南
  • Nginx
    • 一次 nginx 分享
  • Git
    • Git Commit Message 须知
    • .gitignore 模板
    • git tag标签
  • 摄影
    • 摄影基础知识
    • 手机摄影从小白到大师
  • 翻译
    • log4js
    • log4js-node
    • 介绍GitLab上的CI/CD
    • 为GitLab Pages创建并调整GitLab CI/CD
    • 关于 rel=noopener
    • AngularJS 团队 Git 提交信息约定
    • JSON Schema
  • Lifehack
    • 20 个 Google 搜索 Tips 来高效使用 Google
    • 37 个高级 Google 搜索 Tips
Powered by GitBook
On this page
  • 1. 参考资料
  • 2. 整理
  • 2.1 详解JavaScript中的Event Loop(事件循环)机制
  • 2.2 这一次,彻底弄懂 JavaScript 执行机制
  • 2.2.2 说明
  • 2.3 Event Loop的规范和实现
  • 2.3.1 知识点
  • 2.3.2 说明
  • 2.4 代码解析

Was this helpful?

  1. JavaScript

Event Loop

Previousdom-align 源码浅析Next函数实参为对象时的陷阱

Last updated 4 years ago

Was this helpful?

1. 参考资料

2. 整理

其中第二篇资料,更具有说服力,能解释一部分的异步情况。

2.1

2.1.1 知识点

js代码在执行时会将不同变量存放于内存中的不同位置,堆(heap)和栈(stack),其中堆存放着一些对象,栈存放一些基础类型变量以及对象的指针。

执行栈 => 存放同步代码。 事件队列 => 存放异步代码。

当异步事件返回结果(比如定时器时间到期,ajax请求返回完数据)时,js会将异步事件的回调添加到事件队列中。而后在执行栈已经执行完毕情况之后,会查看事件队列是否有任务,如果有任务,则将任务放到执行栈中进行调用。

异步任务分为微任务(micro task)与宏任务(macro task),对应会有微任务事件队列与宏任务事件队列: 宏任务:

  setInterval()
  setTimeout()

微任务:

  new Promise()
  new MutaionObserver()

当前执行栈执行完毕时会立刻处理所有微任务队列中的事件,然后再去宏任务队列取出一个事件,同一次事件循环中,微任务永远在宏任务之前执行

2.1.2 说明

文章后续还提到了 node 中 libuv 的事件循环模型,以及事件循环各个阶段的解释。

2.2.1 知识点

  1. 同步和异步任务进入不同的执行"场所",同步任务进入主线程执行栈,异步任务进入 Event Table 并注册回调函数

  2. 当异步任务指定的事件完成(定时器到时,请求 success )时,Event Table 会把回调函数转移到 事件队列(Event Queue)

  3. 当主线程执行栈的任务执行完毕,会去事件队列读取函数,进入主线程执行

  4. 上述过程不断重复,形成事件循环

setTimeout: 即执行到该代码时,会进入Event Table注册回调函数,当定时器到时之后,Event Table会将回调函数转移到事件队列。

setInterval: 即执行到该代码时,会进入Event Table注册回调函数,之后每隔指定的时间,Event Table会将回调函数转移到事件队列。

除了广义上的同步任务和异步任务,还会将任务划分为宏任务和微任务:

  1. macro-task(宏任务):包括整体代码script,setTimeout,setInterval

  2. micro-task(微任务):Promise,process.nextTick

这个宏任务的划分比2.1资料中的划分多了一个整体代码script

2.2.2 说明

这篇文章举例了一些异步代码的例子,便于理解。同时全文表达出 [执行一个宏任务,执行当前所有微任务]为一个 event-loop。

2.3.1 知识点

process.nextTick 注册的函数优先级高于 Promise, 即微任务从 Event Table 转移到事件队列并不完全根据代码中出现的顺序,还会有优先级的区别。 在评论区中发现以下说法:

process.nextTick 是放到当前同步执行栈的尾部,是一定比异步的任务队列早的。并不是因为优先级高于其他异步的任务。同步的执行栈到异步任务队列,process.nextTick 就是处于中间的那个。 当然异步下,任务队列里就分宏任务 / 微任务。但是我也觉得这里不是由优先级决定的,而是宏任务内的执行栈清空后,再清空当前宏任务内的微任务。接着以此循环再去清空下一个宏任务的执行栈,再清空微任务。

这个说法就相当于说,process.nextTick是在同步执行栈尾部,在执行异步任务队列之前的。就是说它是一个在所有其它异步任务之前的异步任务。(有空再翻资料)

console.log(1)

setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
    process.nextTick(() => {
        console.log(3)
    })
})


process.nextTick(() => {   // 位置1
    console.log(6)
})

new Promise(resolve => { 
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

process.nextTick(() => {   // 位置2
    console.log(6)
})

即process.nextTick这段代码不管是在位置1还是位置2,输出结果都是1,7,6,8,2,4,3,5

同时以下这段代码的返回结果是不确定的:

console.log(1)

setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
    process.nextTick(() => {
        console.log(3)
    })
})

new Promise(resolve => {
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

process.nextTick(() => {
    console.log(6)
})

setTimeout(() => {
    console.log(9)
    process.nextTick(() => {
        console.log(10)
    })
    new Promise(resolve => {
        console.log(11)
        resolve()
    }).then(() => {
        console.log(12)
    })
})

有可能返回1,7,6,8,2,4,3,5,9,11,10,12,但也可能返回1、7、6、8、2、4、9、11、3、10、5、12,即两个setTimeout的执行可能混合。

原因在于:

setTimeout(() => { // 1

})

setTimeout(() => { // 2

})

这两段 setTimeout 都没有指定 timeout时间,默认是0,延时相同,被合并进同一个expired timers queue而一起执行。

当做合并为同一个宏任务?,那为啥还会有两种可能的结果?一直返回1、7、6、8、2、4、9、11、3、10、5、12 才对吧,这一块还没有理解

2.3.2 说明

2.4 代码解析

示例一

setTimeout(() => {
  console.log("clock1")
  setTimeout(() => {
    console.log('clock3')
  },200)
},1000)

setTimeout(() => {
  console.log("clock2")
},2000)

以上这段代码最终打印clock1, clock3, clock2.

首先全局代码执行
1. 遇到异步任务,1000的定时器,加入到event table
2. 遇到异步任务,2000的定时器,加入到event table
3. 没有微任务,则进行事件循环
4. 进行到1000ms,将1000的定时器,加入到事件队列,且为宏任务
5. 此时宏任务中有1000ms这个事件
6. 没有微任务,执行事件循环
7. 有同步任务,则打印 clock1,
8. 有异步任务,将200ms的定时器加入到 event table
9. 没有微任务,则进行事件循环
10. 到了1200ms,将200的定时器,加入到事件队列,且为宏任务
11. 此时宏任务中有200ms这个事件
12. 没有微任务,执行事件循环
13. 有同步任务,则打印 clock3
14. 没有微任务,则进行事件循环
15. 到了2000ms,将2000的定时器,加入到事件队列,且为宏任务
16. 此时宏任务中有2000ms这个事件
17. 没有微任务,则进行事件循环
18. 有同步任务,则打印 clock2

示例二

setTimeout(() => {
  console.log("clock1")
  setTimeout(() => {
    console.log('clock3')
  },0)
},0)

setTimeout(() => {
  console.log("clock2")
},0);

以上这段代码始终打印clock1, clock2, clock3

首先全局代码执行
1. 遇到异步任务,0的定时器,加入到event table
2. 遇到异步任务,0的定时器,加入到event table
3. 没有微任务,则进行事件循环
4. 进行到0ms,将两个0的定时器,加入到事件队列,且为宏任务
5. 此时宏任务中有两个0ms的事件
6. 没有微任务,执行事件循环
7. 执行第一个宏任务,(第二个宏任务暂时不执行),其中有同步任务,则打印 clock1,
8. 有异步任务,将0ms的定时器加入到 event table
9. 没有微任务,则进行事件循环
10. 到了0ms,将0ms的定时器,加入到事件队列,且为宏任务
11. 此时宏任务中有【之前剩余的宏任务二,当前】
12. 没有微任务,执行事件循环
13. 有同步任务,则打印 clock2
14. 没有微任务,执行事件循环
15. 有同步任务,则打印 clock3

2.2

2.3

这篇文章是在阅读 2.2之后写的,因此很适合和 2.2 连着一起看,同时还翻阅了 blink 与 node的源码解释了定时器0ms和1ms为什么效果是一致的,很具说服力。

针对这种嵌套调用的情况,可以根据去理解:

详解JavaScript中的Event Loop(事件循环)机制
这一次,彻底弄懂 JavaScript 执行机制
Event Loop的规范和实现
这一次,彻底弄懂 JavaScript 执行机制
详解JavaScript中的Event Loop(事件循环)机制
这一次,彻底弄懂 JavaScript 执行机制
Event Loop的规范和实现
这一次,彻底弄懂 JavaScript 执行机制
这一次,彻底弄懂 JavaScript 执行机制