← 返回目录

浏览器:事件循环

第 98 / 154 卷日期:

  1. 功能检测,根据每个浏览器独有的特性来进行判断,如 ie 下独有的 ActiveXObject。

浏览器:事件循环

  1. Event Loop

Js引擎在执行代码时候会产生执行栈,当调用异步 API,例如 setTimeout,setInterval,Promise 等回调触发时,就会进入异步任务队列,当同步代码执行完成后,就会去异步队列取出回调函数并执行,这样就形成

了一个事件循环。在 Javascript 中有两种任务类型,分别是宏任务和微任务。

事件循环唯一任务:将队列和调用堆栈连接起来。

事件队列是一个存储执行任务的队列。

执行栈类似于函数调用栈的运行容器,当其为空时,JS检查事件队列,将第一个任务压入栈中执行。若调用栈和微任务队列为空,事件循环检查宏任务队列是否还有任务,从中弹出进入调用栈执行完再弹出。

setTimeout的时间不是指马上执行,而是最快可以多久后执行,因为它会等待调用栈为空时执行。

浏览器的渲染必须要调用栈为空时才会执行,正常的 forEach 会阻塞渲染。

在一个回调出栈,另一个回调进栈的间隙(此时栈空),渲染得以顺利进行。

单线程JS实现异步

浏览器的内核多线程实现。

script是宏任务

若存在两个script代码块,首先执行第一个 script 中的同步代码,若这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,首先清空微任务队列,再去开启第二个 script 的执行。

js单线程问题

所有任务都在一个线程上完成,一旦遇到大量任务或遇到一个耗时的任务,网页就可能出现卡死,也无法响应用户的行为。

setTimeout如果在主线程上运行就会阻塞其他活动。

所以,需要做的就是,离开这个线程,同时运行这个任务。

JavaScript的单线程,与它的用途有关。

作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

Event Loop

是一个程序结构,用户等待和发送信息的事件。

microtask在事件循环的macrotask执行完之后执行。

macro(宏任务)有多个,微任务(micro)队列只有一个。

先执行宏任务,再执行微任务,执行宏任务的过程中遇到微任务,依次加入微任务队列。

异步实现

  1. 宏观:浏览器多线程(从宏观来看是多线程实现了异步)
  2. 微观:Event Loop,事件循环(Event Loop 翻译是事件循环,是实现异步的一种机制)

常见的macrotask有:(一般由浏览器发起)DOM渲染后触发

  1. script整体代码
  2. setImmediate:node 的方法
  3. setTimeout 和 setInterval
  4. requestAnimationFrame
  5. I/O
  6. UI rendering

常见的microtask有:(一般由JS自身创建)DOM渲染前触发

  1. process.nextTick (Node环境中)
  2. Promise callback(例如 promise.then)
  3. Object.observe (基本上已经废弃)
  4. MutationObserver

运行过程

线程都有自己的运行数据存储空间,堆的空间比较大,所以存储一些对象;

栈的空间比较小,所以存储一些基础数据类型、对象的引用,函数的调用;

函数调用就入栈,执行完函数体的代码自动从栈中弹出——调用栈。

当栈中的函数出栈时,栈为空的话,我们会调用一些异步函数,这个异步函数会找它们的异步处理模块,异步处理模块包括定时器、promise、Ajax等,异步处理模块会找它们各自对应的线程,线程向任务队列中添加事件,再从

任务队列中取出事件,去执行对应的回调。

3个注意点:

  1. 整个script代码块属于宏任务
  2. 当宏任务执行完,会去执行所有微任务
  3. 微任务执行完再去执行下一个宏任务,等调用栈为空时执行一个微任务;调用栈不为空时,任务队列的微任务

一直等待;微任务执行完又去取任务队列的宏任务,依次执行宏任务,执行宏任务时检查当前是否存在微任务,若有微任务就去执行完所有微任务,然后再去执行后续的宏任务。

注意点:

  1. 一个 Event Loop 有一个或多个 task queue(任务队列)
  2. 每个 Event Loop 有一个 microtask queue(微任务队列)

微任务(一个 promise),再执行下一个宏任务(setTimeout)

记住一点,要把本次宏任务下所产生的微任务全部执行完才会执行下一个宏任务,记住是产生的,没有产生的不会执行!

//async函数是对一些异步操作的处理方式,一旦调用会立即执行,其中可包含微任务和宏任务 // await语句后面的代码回放进微任务队列执行

async function async1() {

console.log('async1 start')

//await是等待,需要把第一轮微任务执行完,再执行下面的内容

//await后面的内容执行完,又执行宏任务

await async2()

console.log('async1 end')

}

async function async2() {

return Promise.resolve().then(_ => {

console.log('async2 promise')

})

}

console.log('start')

setTimeout(function () {

console.log('setTimeout')

}, 0)

async1()

new Promise(function (resolve) {

console.log('promise1')

resolve()

}).then(function () {

console.log('promise2')

//async1 start

//promise1

//async2 promise

//promise2

//async1 end

//setTimeout

所谓一轮事件循环就是第一轮宏任务和微任务结束。当微任务队列清空后,一个事件循环结束。

所以正确的一次 Event loop 顺序是:

  1. 执行同步代码,这属于宏任务
  2. 执行栈为空,查询是否有微任务需要执行
  3. 执行所有微任务
  4. 必要的话渲染 UI
  5. 然后开始下一轮 Event loop,执行宏任务中的异步代码

通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的界面响应,我们可以把操作 DOM 放入微任务中。

  1. Node事件循环

microtask 在事件循环的各个阶段之间执行(一个阶段执行完毕,就会去执行microtask队列任务)。和浏览器不同,浏览器时每次取一个宏任务执行,执行完后就跑去检查微任务队列.

但nodejs是来都来了,一次全部执行完该阶段的任务好了,那么process.nextTic和微任务啥时候执行?

process.nextTick会优先于微任务.

事件循环顺序

图 178-1
图 178-1

执行的几个阶段

  1. timers 阶段:执行 timers 的回调,也是执行 setTimeout 和 setInterval 的回调
  2. pending IO callbacks:系统操作的回调,如定时器和setImmediate回调。
  3. idle,prepare:内部使用
  4. poll:等待新的 I/O 事件进来,其他所有宏任务都属于poll阶段,此阶段,系统会做两件事:
  5. 执行到点的定时器
  6. 执行 poll 队列中的事件
  7. 且当 poll 中没有定时器的情况下,会发生以下两件事

如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制

如果 poll 队列为空,会有两件事发生

如果有 setImmediate 需要执行,poll 阶段会停止并且进入到 check 阶段执行 setImmediate 如果没有 setImmediate 需要执行,会等待回调被加入到队列中并立即执行回调

  1. 如果有别的定时器需要被执行,会回到 timer 阶段执行回调。
  2. check:执行 setImmediate 回调
全站分卷索引 · 154 卷
  1. 1代码随想录知识星球精华(最强八股文)第五版(前端篇))
  2. 2html语义化
  3. 3src 和 href 的区别
  4. 4link 与 @import 的区别
  5. 5iframe
  6. 6CSS
  7. 7伪元素和伪类
  8. 8block,inline,inline-block
  9. 9重排、重绘和合成
  10. 10盒子模型
  11. 11margin 合并
  12. 12margin和padding 的值为百分比时
  13. 13浮动
  14. 14定位
  15. 15CSS选择器
  16. 16CSS选择器优先级
  17. 17选择器权重Specificity
  18. 18CSS属性继承
  19. 19不可/可继承属性
  20. 20display: none与 visibility: hidden的区别
  21. 21background-size
  22. 22client、offset&scroll
  23. 23CSS3新特性
  24. 24flex布局
  25. 25常见布局
  26. 26三栏布局
  27. 27垂直居中
  28. 28水平垂直居中
  29. 29CSS画三角形
  30. 30使chrome支持12px以下文字
  31. 31响应式布局
  32. 32媒体查询
  33. 33过渡与动画
  34. 34JavaScript 动画 和 CSS动画
  35. 35SCSS
  36. 36CSS性能优化
  37. 37有哪些数据类型
  38. 38判断数据类型
  39. 39类型转换
  40. 40parseInt&parseFloat
  41. 41浮点数求和步骤
  42. 42十进制小数转二进制
  43. 430.1+0.2 为什么不等于 0.3
  44. 44undefined 和 undeclared 的区别
  45. 45JavaScript NaN 属性
  46. 46isFinite()&isNaN()
  47. 47数组
  48. 48对原数组有影响
  49. 49对原数组无影响
  50. 50数组去重
  51. 51数组拍平
  52. 52ES6新增的数组方法
  53. 53判断是否存在某个值
  54. 54arguments为啥不是数组
  55. 55对象
  56. 56遍历对象的方法
  57. 57判断对象是否具有属性
  58. 58for in 和 for of 的区别
  59. 59继承方式
  60. 60instanceof
  61. 61深浅拷贝
  62. 62freeze属性
  63. 63函数
  64. 64函数柯里化
  65. 65函数的 length
  66. 66箭头函数和普通函数区别
  67. 67构造函数
  68. 68执行上下文
  69. 69作用域
  70. 70作用域和作用域链
  71. 71闭包和内存泄漏
  72. 72this
  73. 73关于 this 的指向问题
  74. 74改变this指向的方法(bind / call / apply)
  75. 75字符串
  76. 76DOM和事件
  77. 77哪些事件支持冒泡
  78. 78阻止冒泡
  79. 79HTMLCollection、NodeList
  80. 80dom.onclick 和 dom.addEventListener的区别
  81. 81e.target和e.currentTarget
  82. 82页面生命周期
  83. 83异步
  84. 84内存管理GC
  85. 85其它
  86. 86JavaScript 中日期时间格式转换
  87. 87input和object实现双向绑定
  88. 88JSON.stringify()
  89. 89escape、encodeURI&encodeURIComponent
  90. 90移动端滚动穿透和溢出
  91. 91ES6
  92. 92Map字典
  93. 93... 与rest
  94. 94proxy
  95. 95模块化
  96. 96Ajax、Fetch和Axios
  97. 97Webpack
  98. 98浏览器:事件循环
  99. 99浏览器内核
  100. 100浏览器同源策略
  101. 101输入URL回车后发生了什么
  102. 102HTTP
  103. 103HTTP 2.0
  104. 104浏览器存储
  105. 105鉴权
  106. 106Webstorage
  107. 107浏览器执行机制
  108. 108AST 抽象语法树
  109. 109性能优化
  110. 110CSS
  111. 111网络相关
  112. 112使用HTTP2.0
  113. 113图片
  114. 114CDN图片
  115. 115其他文件优化
  116. 116降低APP首页开屏渲染时间
  117. 117页面白屏
  118. 118HTTPS中间人攻击
  119. 119Web Worker
  120. 120Service Worker
  121. 121微前端
  122. 122Vue
  123. 123v-show 和 v-if 的区别
  124. 124双向数据绑定的原理
  125. 125Vue中的插槽使用
  126. 126Vue中的key
  127. 127Vue过滤器
  128. 128Vue中Scoped原理
  129. 129Vue中$nextTick的使用
  130. 130Vue中实现组件通信的方式
  131. 131Vue-router
  132. 132Vue路由传参
  133. 133前端路由
  134. 134Vuex
  135. 135虚拟DOM
  136. 136DIFF 算法
  137. 137Vue3和Vue2有哪些区别
  138. 138说一说你对React的理解
  139. 139React事件机制
  140. 140说一说你对react refs的理解
  141. 141react中key的作用
  142. 142组件开发
  143. 143有状态组件和无状态组件
  144. 144函数式组件和类组件的区别
  145. 145PureComponent&Component
  146. 146常见 Hooks
  147. 147状态管理
  148. 148redux toolkit
  149. 149React18新特性
  150. 150client中的createRoot取代以前的ReactDOM.render
  151. 151Suspense
  152. 152新的Hooks
  153. 153VDom一定更快吗
  154. 154Fiber