优化过程
总的策略是:对于所有执行时间长的组件,其子组件采用分片挂载。
首先,我们定义一个通用的分片执行方法:
/**
* 分片执行任务列表,避免一次性执行造成 JS 阻塞
* 优先使用 requestIdleCallback,不支持则使用 setTimeout
* @param {Array<function>} tasks 任务列表
*/
export function fiberExecute(tasks) {
const _tasks = [...tasks];
const fun = () => {
// 去除第一个任务
const task = _tasks.shift();
if (task) {
// 如果是函数,执行
typeof task === 'function' && task();
// 还有任务,继续加入事件循环
if (_tasks.length) {
myRequestIdleCallback(fun);
}
}
};
myRequestIdleCallback(fun);
}
function myRequestIdleCallback(fun) {
if (window.requestIdleCallback) {
requestIdleCallback(fun);
return;
}
setTimeout(() => {
requestIdleCallback(fun);
}, 0);
}
对全局组件,采用分片挂载:
this.keys.map(k => () => (this[k] = true));:创建一个函数数组 tasks,每个函数的功能是将当前实例中的一个属性设置为 true。这些属性名存储在 this.keys 数组中。这可能是用于控制其他组件的显示或状态。
// 使用 v-if 控制先不挂载
<component-config v-if="isShowComponentConfig" />
// global-dialogs mounted 后分片挂载子弹框
mounted() {
// 全局弹框太多,一次性渲染需要耗时500ms+,会造成JS阻塞,这里采用分片渲染
const tasks = this.keys.map(k => () => (this[k] = true));
fiberExecute(tasks);
}
组件注册分片执行。由于这里单个组件执行时间一般不超过 5ms,为了尽快完成组件挂载,这里采用一次挂载 6 个组件,粒度可以自己控制。也就是分片策略的粒度,我们可以自己控制。
import { fiberExecute } from 'utils/fiber';
export function installComponents(_vue, components) {
const keys = components.keys();
// 每次处理个数
const len = 6;
const keysArr = [];
let cur = 0;
while (cur < keys.length) {
keysArr.push(keys.slice(cur, cur + len));
cur += len;
}
const tasks = keysArr.map(ks => () => {
ks.forEach(key => {
// 注册逻辑
});
});
fiberExecute(tasks);
}
还有插件注册、feedback 按钮挂载时机等所有高耗时的任务都可以使用分片执行策略。
目前的分片执行最小粒度为组件,如果单个组件执行过长,还可以继续对该组件进行拆分。
采用分片执行后,可以看到 JS 任务执行时间基本是均匀,避免了出现大的任务块,一直占用渲染进程的情况。
补充
补充:分片执行(chunked execution)是「长任务分批调度」的常见思路,核心是借助浏览器的空闲回调或微任务队列,避免一次性同步逻辑占用主线程。



