关于时间切片,本文将从以下三方面介绍:
- 什么是时间切片?
- 时间切片的作用是什么?
- 如何实现时间切片?
本文内容会使用堆的一些操作,可戳 最小堆 文章了解。
什么是时间切片?
其实就是一个时间段,比如 5ms。
时间切片的作用是什么?
避免高优先级任务被延迟执行,比如在输入框中输入关键字进行搜索,是高优先级的,如果被延迟执行,就会造成页面卡顿的现象,搜索结果和输入的关键字不一致。
如何实现时间切片?
先来了解几个概念。
任务(task)
类型如下:
type Task = {
id: number;
callback: Callback | null;
priorityLevel: PriorityLevel;
startTime: number;
expirationTime: number;
sortIndex: number;
};
执行任务,实际上就是执行任务的 callback 函数。
每个 task 都有一个 callback 函数,callback 执行完了,就执行下一个 task。
工作单元(work)
一个 work 就是一个时间切片内执行的一些 task。时间切片要循环,就是 work 要循环(loop)。
workLoop
workLoop 是实现时间切片的关键函数,顾名思义就是循环 work。
// 任务池,最小堆
const taskQueue: Array<Task> = [];
// 返回为 true,表示还有任务没有执行完,需要继续执行,返回 false 表示任务执行完了
function workLoop(initialTime: number): boolean {
let currentTime = initialTime;
// 从任务池里取出任务
currentTask = peek(taskQueue);
while (currentTask !== null) {
// 过期时间大于当前时间,并且需要让出主线程(JS 是单线程),过期时间可以理解为开始执行的时间
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
break;
}
// 执行任务,其实就是执行它的 callback 函数
const callback = currentTask.callback;
if (typeof callback === "function") {
// 有效的任务
currentTask.callback = null; // 防止任务被重复执行
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
// 任务没有执行完
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
// 任务没有执行完,放在 callback 中,下次继续执行
currentTask.callback = continuationCallback;
return true;
} else {
// 任务执行完,并且是堆顶任务,直接删除
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
} else {
// 无效的任务,callback = null,直接删除
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// while 循环完,看任务是否执行完
if (currentTask !== null) {
// 任务没有执行完
return true;
} else {
// 任务执行完了
return false;
}
}
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
return true;
}