time slice

时间分片是什么

把长任务分割成若干个小任务执行,比如1s 改成 10 个 100ms 来执行

实现

两个API

  • window.requestAnimationFrame

  • window.requestIdleCallback

window.requestAnimationFrame

  • 告诉浏览器执行一个动画,并在下次重绘之前调用指定的回调函数

  • 若想继续更新下一帧动画,那么回调函数自身必须再次调 window.requestAnimationFrame

  • 回调函数会被传入DOMHighResTimeStamp参数,指示当前被 requestAnimationFrame 排序的回调函数被触发的时间

单一帧

1
2
3
window.requestAnimationFrame((e) => {
console.log(e, 'eee')
})

每一帧都执行

1
2
3
4
5
6
7
const renderAnimation = () => {
window.requestAnimationFrame((e) => {
console.log(e)
renderAnimation()
})
}
renderAnimation()

优点

  • requestAnimationFrame 兼容性非常好

  • 保持与设备相同的刷新频率

  • 执行时机处在每一帧的 css 计算之前,避免重复计算

window.requestIdleCallback

  • 在浏览器的空闲时段内调用的函数

  • 函数一般会按先进先调用的顺序执行,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序

单一帧

1
2
3
window.requestIdleCallback((e) => {
console.log(e, 'eee')
})

每一帧

  • 每一帧,并不一定是每一帧,要看该帧执行完后是否有空余时间,如果有,则执行

  • 指定 timeout 字段,可以保证让回调在超过 timeout 指定的时间后,执行一次。如果不指定,回调可能几秒钟都不会执行(浏览器一直没有空闲帧)

1
2
3
4
5
6
7
const renderAnimation = () => {
window.requestIdleCallback((e) => {
console.log(e.didTimeout)
renderAnimation()
}, { timeout: 5} )
}
renderAnimation()

DocumentFragment

  • 一个没有父对象的最小文档对象。作为一个轻量版的 Document 使用,存储由节点(nodes)组成的文档结构

  • 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题

  • 最常用的方法是使用文档片段作为参数承接appendChild, insertBefore 方法添加的节点

  • 然后将 DocumentFragment 添加到指定的dom位置,这种情况下片段的所有子节点, 而非片段本身。因为所有的节点会被一次插入到文档中,只进行一次重渲染的操作

  • 可以使用document.createDocumentFragment 方法或者构造函数来创建一个空的 DocumentFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// HTML
<ul class="list"></ul>

// JS
let listData = document.querySelector('.list')
let text = ['1', '2', '3']

let fragment = new DocumentFragment()

text.forEach(function (m) {
let li = document.createElement('li')
li.innerHTML = m
// 利用 DocumentFragment 充当介质,可以一次性将多个 li 插入。不必引入额外的标签
// DocumentFragment 并非真实的 dom 不会插入新的 dom 实体
// 也不会产生重绘操作
fragment.appendChild(li)
})

listData.appendChild(fragment)

实现几万数据的分片渲染

  • 接下来简单实现一个5万数据加载,如果一次性加载肯定是免不了卡顿的

  • 将5万数据分到每一帧(16ms)处理,一帧加载20条

优点

  1. 首次加载不会卡顿很久白屏

  2. 滑动滚动条时,不会出现严重的闪屏

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root"></div>
<style>
* {
margin: 0;
padding: 0;
}
.list {
width: 300px;
left: 50%;
}
.list li {
border-bottom: 1px solid #ddd;
margin-bottom: 6px;
}
</style>
<ul class="list"></ul>
<script>
let list = document.querySelector('.list')
let total = 50000
let curPage = 20
let index = 0
const render = (total, index) => {
if (total <= 0) {
return
}
// requestAnimationFrame 每一帧都执行 ==========
window.requestAnimationFrame((e) => {
console.log(e)
let domFra = new DocumentFragment()
for (let i = 0; i < curPage; ++i) {
let item = document.createElement('li')
item.innerText = `我是${index + i}`
domFra.appendChild(item)
}
list.appendChild(domFra)
render(total - curPage, index + curPage)
})
}
render(total, index)
</script>
</body>
</html>
返回
顶部