一系列问题

  • 什么是异步?有哪些异步,异步都是一样的吗?
  • setTimeout 是定时器?
  • 什么是事件循环?为什么要有事件循环
  • 任务,微任务,调用栈
  • 浏览器渲染帧
  • 一个 click 事件,绑定了两次回调(同个元素绑定两次,或者通过事件冒泡的关系绑定 2 次),请问触发 click 的时候,算一次任务,还是 2 次任务。
  • 写了多个 setTimeout,超时时间设置不同。
  • requestAnimationFrame
  • requestIdleCallback

引子

React 中的 setState 是同步还是异步?

https://codesandbox.io/s/setstate-voffg?file=/src/App.js
https://zh-hans.reactjs.org/docs/react-component.html#setstate
https://zh-hans.reactjs.org/docs/faq-state.html#what-does-setstate-do

webstorm 写的小案例

队列

队列

js 代码是如何执行的

https://codesandbox.io/s/call-stack-8zv4b?file=/src/index.js
堆和栈:函数调用是如何影响到内存布局的?

现实举例:

ceo 想办件事:
ceo 找总监,总监又找了主管,主管又找了某位一线员工,事情完成了,一线员工告诉主管,主管再汇报给总监,总监再汇报给 ceo

事件循环 event loop

首先上图,分析一下上面的代码执行
image.png

但是上面这个图,大体上是正确的,也不是完全正确。
知其然,要知其所以然。
事件循环为什么是这样子的。

首先,为什么要有事件循环。
参考:消息队列和事件循环:页面是怎么“活”起来的?

现实举例

只要去办事,就会有窗口排队这件事。
门诊,加急这回事。理解微任务。

任务

script

setTimeout

参考:WebAPI:setTimeout是如何实现的?

  • setTimeout 是延迟任务,如果检查延迟任务列表的时候,发现不只一个到期了。应该如何做。

    还是得排序,按预期执行时间先后来执行。每一个都是一个任务。

  • 普通消息队列 和 延迟消息队列,先处理哪个

    先处理延迟消息队列,才能尽力保证延迟时间相对正确

setInterval

参考:调度:setTimeout 和 setInterval

  • 常常看见别人说用 setTimeout 模拟 setInterval,为什么

XMLHttpRequest

参考:WebAPI:XMLHttpRequest是怎么实现的?

xhr 和 fetch 的区别
参考: Fetch与Promise的探讨,ajax的替代品

UI 事件

onClick。。。。。

requestAnimationFrame

requestIdleCallback

微任务

Promise

MutationObserve

UI 渲染


结论

什么是异步?有哪些异步,异步都是一样的吗?

本质上是说函数调用。 执行当前任务的时候,如果某个函数最终执行的时机是脱离当前调用栈,那么这个函数就是异步执行的。异步有很多类型。大体上可以分为任务,和微任务。

setTimeout是任务。Promise.resolve().then是微任务。任务和微任务的执行时机有差异,任务的粒度控制更粗一些,精确度不够高。微任务的粒度控制更细一些,见缝插针。requestAnimationFrame的执行时机是个黑盒,由系统安排。

setTimeout 是定时器

我觉得中文世界一直叫定时器,真的是误解,从某种具体的使用场景命名,真的是无语。从英文字面意思来看,设置一个超时。更确切的说,就是延迟执行,就是 delay,但是 delay 又不能无期限。所以得设置超时时间。但是这个超时时间又是不精确的。取决于主线程是否繁忙。

setTimeout 是延迟任务,如果检查延迟任务的时候,发现不只一个到期了。应该如何做。还是得排序,按预期执行时间先后来执行。每一个都是一个任务。


什么是事件循环?为什么要有事件循环

主线程是单线程。同个时间点只能干一件事。要干的事情太多了,用户交互,延迟调用,网络回调。。。。
先干什么,后干什么,无法随性而定。做事情得讲规矩。在主线程的地盘,事件循环就是这个规矩。
事件循环的本质就是协调和秩序。那问题是事件循环主体是谁。

事件循环主体

js 的运行环境,浏览器端是浏览器渲染进程的主线程,node 端。而且两者的事件循环的机制不是完全一致。

事件循环本质

用户代理协调机制。

事件循环特点

浏览器端:任务-》微任务队列-》UI 渲染(可选)
node 端:不同版本有差异。旧版 多个任务-》微任务队列,新版和浏览器端对齐

image.png

任务,微任务,调用栈

从发展的观点来看,微任务是后面引入的。任务简单来说,就是事情一件件干。一件事情就是一个任务。执行一个任务的时候,往往就是函数调用(可能发生嵌套调用)。管理函数调用,就得使用调用栈。这里的栈,既是指内存空间,也有数据结构的含义。因为任务的调度机制粒度比较粗,所以有些场景没法满足(改用同步的话,可能又有性能问题),最终权衡之下引入了微任务,微任务就是个加塞,见缝插针的东西,js 控制权更强一些。

参考文章:宏任务和微任务:不是所有任务都是一个待遇

任务

系统安排,可能安排更多非 JS 任务,JS 控制权比较弱一些。

微任务

加塞,见缝插针,JS 控制权更强一些。

浏览器渲染帧

浏览器渲染帧。和系统显示刷新频率有关系。为了避免浪费,浏览器渲染的频率和显示刷新频率保持一致比较好。比如显示刷新 60hz,平均每帧 1000/60 = 16.67ms。浏览器在16.67ms 刷新一次刚好,否则,刷新再多,计算器的显示设备也不会显示,那不是浪费吗。UI render 在一轮事件循环结束后是一个可选的阶段。由系统信号决定要不要 render,而且这个部分没有明确标准,浏览器可以自主决定。所以,得注意。

帧和事件循环的关系

一帧就是一次画面更新。可能包含了多次的 event loop。如果要UI render,就会执行requestAnimationFrame的回调。requestIdleCallback 可能执行,可能不执行。

requestAnimationFrame

除了动画,还可以防抖。但是回调本身别太复杂。

requestIdleCallback

参考:熟悉requestidlecallback到了解react ric polyfill实现
安排一些非重要任务。

https://github.com/funfish/blog/issues/30
https://segmentfault.com/a/1190000039287134
https://www.zhuyuntao.cn/React%E4%B8%ADrequestIdleCallback%E7%9A%84polyfill%E5%AE%9E%E7%8E%B0

一个 click 事件,绑定了两次回调,最终触发算几次任务

看情况,如果是用户点击,本质上由交互触发,算 2 次任务。如果是程序触发,算是一次任务。什么叫程序触发。比如 button.click(), button.dispatchEvent(evt)。

Promise

参考: Promise:使用Promise,告别回调函数

async await

参考:async/await:使用同步的方式去写异步代码

await this.setState 是一个巧合。

setState本质不是异步,还是在当前调用栈上的延迟调用。

测试

参考文章

深入探究 eventloop 与浏览器渲染的时序问题
WebAPI:setTimeout是如何实现的?
JavaScript 事件循环:从起源到浏览器再到 Node.js
硬核到极致的 Event Lo 讲解op,不信你看完还不会