各个观察api的执行顺序
wǎng luò shí huāng 2024-07-23
学习es5
各个观察api的执行顺序
浏览器处理每一帧的流程*
- 参考:https://mp.weixin.qq.com/s/uRQwy0X7x05gVG8uJYBY0g
- 【Input events】处理用户事件,先处理【阻塞事件Blocking】包括touch和wheel事件,后处理【非阻塞事件Non-blocking】包括click和keypress
- 【JS】处理完用户事件后执行【定时器Timers】
- 【Begin frame】处理完定时器后开始进行【每帧事件Per frame events】的处理,包括窗口大小改变、滚动、媒体查询的更改、动画事件。
- 【rAF】处理完帧事件后执行requestAnimationFrame回调函数和IntersectionObserver回调函数。
- 【Layout】然后【重新计算样式Recalc style】、【更新布局Update layout】、【调整Observer回调的大小Resize Observer callbacks】
- 【Paint】然后【合成更新Compositing update】、【Paint invalidation】、【Record】
requestAnimationFrame是同步执行,在浏览器绘制结束之后,下次绘制之前执行。参考:https://mp.weixin.qq.com/s/y8cf_vCUNSNdM4NlFj-2JA 里面的useEffect和useLayoutEffect的区别
我们要避免在此api里执行时间较长的代码:
https://www.jianshu.com/p/36e1f6d2ae57
使用
cancelAnimationFrame:如果动画过程中遇到特殊情况需要暂停动画,可以使用cancelAnimationFrame来停止当前正在执行的动画帧,避免不必要的资源浪费。requestAnimationFrame() 只有当标签页处于活跃状态是才会执行,当页面隐藏或最小化时,会被暂停,页面显示,会继续执行,节省了CPU 开销。
利用这个特性,我们可以写出,只有当前页处于激活状态才会运行的代码
function pageInActiveBackPromise( outerFunction, { pagePathName = "", // 必填,页面的pathName urlNotMatchRun = false, }: { pagePathName: string; urlNotMatchRun: boolean } ) { // pagePath:调用当前函数页面的pathName return new Promise((resolve, reject) => { // 页面处于非激活状态过 (function callFunction(flag: boolean | number) { // window.requestAnimationFrame会自动传入time参数 if (flag === false) { resolve("不会再往后执行,直接返回"); return; } if (flag === true) { const result = outerFunction?.(); if (Object.prototype.toString.call(result) == "[object Promise]") { result.then?.((data) => resolve(data))?.catch((err) => reject(err)); } else { resolve(result); } return; // 只让window.requestAnimationFrame执行一次就行 } // if (pageHiddenRun) { // 当前页面处于非激活状态时,document.hidden = true,所以更靠谱的使用document.hidden去判断 // 如果页面处于非激活状态,那么callFunction函数将不会被调用,而是被挂载等待;当页面再次处于激活状态时,那么callFunction函数会被调用。 // window.requestAnimationFrame(callFunction); // window.requestAnimationFrame可以改为监听路由变化,进而控制callFunction函数在对应的路径下时才执行。可以把callFunction函数压入一个数组里,路由变化时取出来执行或者暂停取出数组里的函数进而不执行。js监听路由变化:https://www.jb51.net/article/219730.htm或者https://www.npmjs.com/package/location-bar // } else { // 一般单页开发比较多 ManageFunction.addFunction({ func: callFunction, pagePathName, urlNotMatchRun }); // } })(0); }); } // 使用示例:await pageInActiveBackPromise(()=>Promise.resolve(789)) /** 上面的window.requestAnimationFrame(callFunction)可以换成如下: ManageFunction.addFunction({ func: callFunction, pagePathName, urlNotMatchRun })。 那么就可以限制window.location.pathname和参数item.pagePathName不匹配的callFunction不执行outerFunction了。 */ import LocationBar from "location-bar"; class ManageFunction { static funcs: Set<{ func: Function; pagePathName: string; urlNotMatchRun: boolean }> = new Set(); static addFunction(params: { func: Function; pagePathName: string; urlNotMatchRun: boolean }) { this.funcs.add(params); if (!this.isRun) { this.run(); } } static isRun = false; static async run() { this.isRun = true; for (const item of this.funcs) { // 默认页面隐藏或者页面路径与参数item.pagePathName不匹配时,依然保存在set中不去执行。 if (item && !document.hidden && ((item.pagePathName && window.location.pathname.includes(item.pagePathName)) || item.urlNotMatchRun)) { // 让addFunction可以加函数进来 await item.func(true); this.funcs.delete(item); } // 默认页面路径与参数item.pagePathName不匹配时,不执行后续函数。 if (item && !item.urlNotMatchRun && item.pagePathName && !window.location.pathname.includes(item.pagePathName)) { await item.func(false); this.funcs.delete(item); } } this.isRun = false; } } // 路由变化时执行ManageFunction.run() const locationBar = new LocationBar(); locationBar.onChange(function () { if (!ManageFunction.isRun) { ManageFunction.run(); } }); locationBar.start(); // 页面可见时执行ManageFunction.run() document.addEventListener("visibilitychange", function () { if (document.visibilityState === "visible") { // 页面可见时执行的代码 if (!ManageFunction.isRun) { ManageFunction.run(); } } });1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90