Appearance
requestIdleCallback
window.requestIdleCallback()
方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。
你可以在空闲回调函数中调用requestIdleCallback()
,以便在下一次通过事件循环之前调度另一个回调。
语法
js
requestIdleCallback(callback)
requestIdleCallback(callback, options)
requestIdleCallback(callback)
requestIdleCallback(callback, options)
1
2
2
参数
callback
一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为
IdleDeadline
的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。
options
(可选)
timeout
如果指定了 timeout,并且有一个正值,而回调在
timeout
毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。
返回值
一个 ID,可以把它传入 Window.cancelIdleCallback()
方法来结束回调。
应用
对应关键代码
vue
<template>
<div class="btn-group">
<button class="btn" @click="onBtnClick">插入{{ total }}个元素</button>
</div>
<div class="box" :ref="el => boxRef = (el as HTMLElement)"></div>
</template>
<script setup lang="ts">
import { ref, h, createApp, VNode } from 'vue'
const total = ref(100000)
const boxRef = ref<HTMLElement | null>(null)
// 插入100000个元素
const onBtnClick = () => {
// 1. 使用vue的方式 卡顿
// const vNodes: VNode[] = []
// for (let index = 0; index < total.value; index++) {
// vNodes.push(h('div', index))
// }
// if (boxRef.value) {
// createApp({ render: () => vNodes }).mount(boxRef.value)
// }
// 2. 使用原生的方式 也卡顿
// const fragment = document.createDocumentFragment()
// for (let index = 0; index < total.value; index++) {
// const div = document.createElement('div')
// div.textContent = `${index}`
// fragment.appendChild(div)
// }
// document.querySelector('.box')?.appendChild(fragment)
const consumer: Consumer<string[] | number> = (item, index) => {
const div = document.createElement('div')
div.textContent = `${index}`
document.querySelector('.box')?.appendChild(div)
}
// 3. 分时执行方式: 虽然执行的总时间没变,但页面没有卡顿了
const chunkSplitor: ChunkSplitor = (nextTask) => {
setTimeout(() => {
nextTask((timeout) => timeout < 16)
}, 30)
}
// const datas = Array.from({ length: total.value }, (_, index) => index.toString())
// preformChunk(datas, consumer, chunkSplitor)
// preformChunk(total.value, consumer)
preformChunk(total.value, consumer, chunkSplitor)
}
type Consumer<T extends number | any[]> = (item: T extends (infer K)[] ? K : T, index: number) => void
type ChunkSplitor = (callback: (nextTask: (timeout: number) => boolean) => void) => void
/**
* 分时执行函数
* @param datas 执行数量或数据集
* @param consumer 每段消费执行的回调
* @param chunkSplitor 自定义分段执行器
*/
function preformChunk<T extends number | any[]>(datas: T, consumer: Consumer<T>, chunkSplitor?: ChunkSplitor) {
const arr: (T extends (infer K)[] ? K : T)[] = Array.isArray(datas) ? datas : new Array(datas)
if (arr.length === 0) {
return;
}
let _chunkSplitor: ChunkSplitor
if (!chunkSplitor) {
if ((globalThis as any).requestIdleCallback) {
_chunkSplitor = (nextTask) => {
requestIdleCallback(idle => {
nextTask(() => idle.timeRemaining() > 0)
})
}
} else {
throw TypeError('当前执行环境没有 requestIdleCallback 函数,请实行定义一个 chunkSplitor 分段执行器。')
}
} else {
_chunkSplitor = chunkSplitor
}
let i = 0 // 目前取出的任务下标
function _run() {
if (i === arr.length) {
return;
}
_chunkSplitor(nextTask => {
const now = Date.now()
while (nextTask(Date.now() - now) && i < arr.length) {
const item = arr[i];
consumer(item, i)
i++
}
_run()
})
}
_run()
}
</script>
<template>
<div class="btn-group">
<button class="btn" @click="onBtnClick">插入{{ total }}个元素</button>
</div>
<div class="box" :ref="el => boxRef = (el as HTMLElement)"></div>
</template>
<script setup lang="ts">
import { ref, h, createApp, VNode } from 'vue'
const total = ref(100000)
const boxRef = ref<HTMLElement | null>(null)
// 插入100000个元素
const onBtnClick = () => {
// 1. 使用vue的方式 卡顿
// const vNodes: VNode[] = []
// for (let index = 0; index < total.value; index++) {
// vNodes.push(h('div', index))
// }
// if (boxRef.value) {
// createApp({ render: () => vNodes }).mount(boxRef.value)
// }
// 2. 使用原生的方式 也卡顿
// const fragment = document.createDocumentFragment()
// for (let index = 0; index < total.value; index++) {
// const div = document.createElement('div')
// div.textContent = `${index}`
// fragment.appendChild(div)
// }
// document.querySelector('.box')?.appendChild(fragment)
const consumer: Consumer<string[] | number> = (item, index) => {
const div = document.createElement('div')
div.textContent = `${index}`
document.querySelector('.box')?.appendChild(div)
}
// 3. 分时执行方式: 虽然执行的总时间没变,但页面没有卡顿了
const chunkSplitor: ChunkSplitor = (nextTask) => {
setTimeout(() => {
nextTask((timeout) => timeout < 16)
}, 30)
}
// const datas = Array.from({ length: total.value }, (_, index) => index.toString())
// preformChunk(datas, consumer, chunkSplitor)
// preformChunk(total.value, consumer)
preformChunk(total.value, consumer, chunkSplitor)
}
type Consumer<T extends number | any[]> = (item: T extends (infer K)[] ? K : T, index: number) => void
type ChunkSplitor = (callback: (nextTask: (timeout: number) => boolean) => void) => void
/**
* 分时执行函数
* @param datas 执行数量或数据集
* @param consumer 每段消费执行的回调
* @param chunkSplitor 自定义分段执行器
*/
function preformChunk<T extends number | any[]>(datas: T, consumer: Consumer<T>, chunkSplitor?: ChunkSplitor) {
const arr: (T extends (infer K)[] ? K : T)[] = Array.isArray(datas) ? datas : new Array(datas)
if (arr.length === 0) {
return;
}
let _chunkSplitor: ChunkSplitor
if (!chunkSplitor) {
if ((globalThis as any).requestIdleCallback) {
_chunkSplitor = (nextTask) => {
requestIdleCallback(idle => {
nextTask(() => idle.timeRemaining() > 0)
})
}
} else {
throw TypeError('当前执行环境没有 requestIdleCallback 函数,请实行定义一个 chunkSplitor 分段执行器。')
}
} else {
_chunkSplitor = chunkSplitor
}
let i = 0 // 目前取出的任务下标
function _run() {
if (i === arr.length) {
return;
}
_chunkSplitor(nextTask => {
const now = Date.now()
while (nextTask(Date.now() - now) && i < arr.length) {
const item = arr[i];
consumer(item, i)
i++
}
_run()
})
}
_run()
}
</script>
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
91
92
93
94
95
96
97
98
99
100
101
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
91
92
93
94
95
96
97
98
99
100
101