一、基本概念
- js是单线程执行的,分为GUI渲染线程,js引擎线程,事件触发线程,定时器触发器线程,http请求线程。
- 执行js会阻塞gui渲染,如js执行所需时间超过一帧的时间,就会导致页面掉帧,也就是肉眼看到的卡顿。
二、场景模拟
1. 准备工作
通过一个小方块平移运动模拟浏览器渲染图形
1.block {
2 position: absolute;
3 left: 100px;
4 top: 100px;
5 width: 50px;
6 height: 50px;
7 background: #f00;
8 animation: move 3s linear infinite alternate;
9}
10
11@keyframes move {
12 0% {
13 left: 100px;
14 }
15 100% {
16 left: 300px;
17 }
18}
使用while模拟js阻塞
1function task() {
2 const now = performance.now()
3 while(performance.now() - now < 1) {}
4}
2. 测试正常运行js耗费时间
1function run1() {
2 const data = new Array(2000)
3 console.time("normal")
4 for(let i = 0; i < data.length; i++) {
5 task()
6 }
7 console.timeEnd("normal")
8}
可以看到总耗时2s,页面在js运行期间卡住了,小方块完全没有响应,直到js运行结束后才正常播放动画。
3. 测试使用宏任务方案延迟计算
1function setTimeoutTask() {
2 return new Promise((s) => {
3 setTimeout(() => {
4 task()
5 s()
6 }, 0)
7 })
8}
9function run2() {
10 const data = new Array(2000)
11 console.time("hong")
12 let list = []
13 for(let i = 0; i < data.length; i++) {
14 list.push(setTimeoutTask())
15 }
16 Promise.all(list).then(() => {
17 console.timeEnd("hong")
18 })
19}
宏任务方案基本原理是利用setTimeout事件会放到下一个宏任务内运行,从而减少了单个渲染帧的计算压力,但还是会出现间断卡顿的现象。
4. 测试使用空闲帧方案延迟计算
1function idleTask(task) {
2 return new Promise((s) => {
3 window.requestIdleCallback((IdleDeadline) => {
4 let timeRemain = IdleDeadline.timeRemaining();
5 if(timeRemain > 0) {
6 task()
7 s()
8 } else {
9 idleTask(task).then(() => {
10 s()
11 })
12 }
13 })
14 })
15}
16function run3() {
17 const data = new Array(2000)
18 console.time("idle")
19 let list = []
20 for(let i = 0; i < data.length; i++) {
21 list.push(idleTask(task))
22 }
23 Promise.all(list).then(() => {
24 console.timeEnd("idle")
25 })
26}
这里用到requestIdleCallback API,MDN上的说明是“这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应”。个人理解就是在每一帧结束后,如果有空闲时间就会调用。
函数回调内有个值”IdleDeadline.timeRemaining()“用于说明当前帧还剩余多少时间。如果大于0,说明有剩余时间可以用于执行js计算。
实际效果确实是没有卡顿感了,但是相应的执行耗时也比普通执行要长
个人笔记记录 2021 ~ 2025