一系列问题
- 什么是异步?有哪些异步,异步都是一样的吗?
- 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
首先上图,分析一下上面的代码执行
但是上面这个图,大体上是正确的,也不是完全正确。
知其然,要知其所以然。
事件循环为什么是这样子的。
首先,为什么要有事件循环。
参考:消息队列和事件循环:页面是怎么“活”起来的?
现实举例
只要去办事,就会有窗口排队这件事。
门诊,加急这回事。理解微任务。
任务
script
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 端:不同版本有差异。旧版 多个任务-》微任务队列,新版和浏览器端对齐
任务,微任务,调用栈
从发展的观点来看,微任务是后面引入的。任务简单来说,就是事情一件件干。一件事情就是一个任务。执行一个任务的时候,往往就是函数调用(可能发生嵌套调用)。管理函数调用,就得使用调用栈。这里的栈,既是指内存空间,也有数据结构的含义。因为任务的调度机制粒度比较粗,所以有些场景没法满足(改用同步的话,可能又有性能问题),最终权衡之下引入了微任务,微任务就是个加塞,见缝插针的东西,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
async await
await this.setState 是一个巧合。
setState本质不是异步,还是在当前调用栈上的延迟调用。
测试
参考文章
深入探究 eventloop 与浏览器渲染的时序问题
WebAPI:setTimeout是如何实现的?
JavaScript 事件循环:从起源到浏览器再到 Node.js
硬核到极致的 Event Lo 讲解op,不信你看完还不会