通过前面的文章,我们已经分析了vue3的响应式系统、render流程、diff算法、生命周期。本文,我们来总结一下vue3源码中涉及的性能优化方法。
1. 编译优化
1.1 PatchFlag 优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processElement -> patchElement
if (patchFlag & PatchFlags.FULL_PROPS) {
// 完整 props 比对
patchProps(el, oldProps, newProps, parentComponent, namespace);
} else {
// 按位运算判断需要更新的内容
if (patchFlag & PatchFlags.CLASS) {
// 仅更新 class
}
if (patchFlag & PatchFlags.STYLE) {
// 仅更新 style
}
if (patchFlag & PatchFlags.PROPS) {
// 仅更新动态 props
}
}
- 编译时标记动态内容
- 运行时按需更新
- 避免全量对比
- 精确定位变化
1.2 Block Tree 优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processElement -> patchElement
if (dynamicChildren) {
// Block 节点仅需要更新动态子节点
patchBlockChildren(n1.dynamicChildren!, dynamicChildren, el, parentComponent);
} else if (!optimized) {
// 全量比对子节点
patchChildren(n1, n2, el, null);
}
- 收集动态节点
- 跳过静态节点
- 减少比对范围
- 提升更新性能
1.3 SSR 优化
// packages/runtime-core/src/renderer.ts
// 调用链: createHydrationRenderer -> baseCreateRenderer -> hydrate
// 服务端渲染优化
if (isHydrating) {
// SSR 水合阶段复用 DOM
if (hydrate(vnode, container)) {
return;
}
} else {
// 客户端渲染
render(vnode, container);
}
- 同构渲染
- 静态标记
- 客户端激活
- 选择性水合
1.4 Tree-shaking 优化
// Vue2 方式: 所有 API 都挂载在 Vue 实例上
import Vue from "vue";
// 即使不用 nextTick,也会打包进来
Vue.nextTick(() => {});
// Vue3 方式: 支持 ES 模块引入
import { nextTick, ref } from "vue";
// 未使用的 API 会在打包时移除
const count = ref(0);
// 1. 按需引入 API
import { ref, computed } from "vue";
// 2. 编译时优化
// template 编译后的代码
import { createVNode, createBlock } from "vue";
// 不使用的功能不会被打包
export function render(_ctx, _cache) {
return (
_openBlock(),
_createBlock("div", null, [
_createVNode("span", null, _toDisplayString(_ctx.msg)),
])
);
}
// 3. 功能标记
// 编译时标记使用了哪些特性
export function render(_ctx, _cache) {
return _withDirectives(
(_openBlock(),
_createBlock("input", {
"onUpdate:modelValue": ($event) => (_ctx.text = $event),
})),
[
[_vModelText, _ctx.text], // v-model 指令标记
]
);
}
Tree-shaking 优化的几个层面:
-
API 级别: Vue2:
- 所有 API 都挂载在 Vue 实例上
- 无法进行 Tree-shaking
- 即使不用某些功能也会打包
- 导致包体积较大
Vue3:
- 支持按需导入 API
- 未使用的 API 不会打包
- 减小最终包体积
- 例如:只使用 ref 不使用 reactive
-
编译优化: Vue2:
- 所有编译功能打包在一起
- 无法剔除未使用的编译器代码
- 运行时包含冗余功能
- 包体积优化空间小
Vue3:
- 编译时分析模板依赖
- 只引入使用到的运行时代码
- 移除未使用的模块
- 例如:没有使用过渡动画,transition 模块不会打包
-
特性标记: Vue2:
- 无特性标记机制
- 所有特性代码都会打包
- 无法按需加载功能
- 冗余代码较多
Vue3:
- 编译时标记使用的功能
- 运行时按需加载功能模块
- 自动剔除未使用特性
- 例如:没有使用 v-model,相关代码不会打包
-
优化效果: Vue2 -> Vue3 的改进:
- 包体积可减少 41% 以上
- 初始渲染速度提升约 55%
- 更新性能提升约 133%
- 内存使用减少约 54%
- 大幅减小生产包体积
- 提升应用加载性能
- 按需加载提升效率
- 减少无用代码引入
2. 运行时优化
2.1 响应式优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> setupRenderEffect -> componentUpdateFn
// 避免组件更新时的递归
toggleRecurse(instance, false);
if (next) {
next.el = vnode.el;
updateComponentPreRender(instance, next, optimized);
} else {
next = vnode;
}
toggleRecurse(instance, true);
- 避免递归更新
- 精确依赖收集
- 异步更新队列
- 防止重复渲染
2.2 缓存优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processElement -> patchElement -> patchProps
// 缓存事件处理函数
if (nextValue !== oldValue) {
hostPatchProp(
el,
key,
oldValue,
nextValue,
namespace,
prevChildren,
parentComponent
);
}
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processComponent -> updateComponent
// 缓存 VNode
if (same) {
newVNode.el = oldVNode.el;
newVNode.component = oldVNode.component;
}
- 事件处理函数缓存
- VNode 复用
- DOM 节点缓存
- Props 值缓存
2.3 渲染优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> setupRenderEffect -> queueJob
// 异步渲染
queueJob(instance.update, instance.suspense);
// 调用链: scheduler.ts -> queueJob -> queueFlush
// 批量更新
if (!queued) {
queued = true;
queueFlush();
}
- 异步渲染队列
- 批量更新策略
- 渲染任务调度
- Suspense 异步加载
2.4 调度系统优化
// packages/runtime-core/src/scheduler.ts
// 调用链: queueJob -> queueFlush -> flushJobs
const queueJob = (job: SchedulerJob) => {
if (!queue.includes(job)) {
queue.push(job);
queueFlush();
}
};
// 调用链: scheduler.ts -> queueFlush -> flushJobs
// 微任务批处理
const resolvedPromise = Promise.resolve();
let currentFlushPromise: Promise<void> | null = null;
const queueFlush = () => {
if (!currentFlushPromise) {
currentFlushPromise = resolvedPromise.then(flushJobs);
}
};
- 任务优先级排序
- 微任务批量处理
- 更新任务去重
- 渲染任务合并
3. 静态优化
3.1 静态提升
// 静态节点提升
// packages/compiler-core/src/transform.ts
// 调用链: transform -> transformElement -> hoistStatic
if (staticCount) {
for (let i = 0; i < staticCount; i++) {
const vnode = vnodes[i];
vnode.patchFlag |= PatchFlags.HOISTED;
hoistedNodes.push(vnode);
}
}
- 静态节点提升
- 静态树提升
- 减少创建开销
- 内存占用优化
3.2 预字符串化
// 静态内容字符串化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> mountElement -> hostInsertStaticContent
if (staticChildren) {
if (isString(staticChildren)) {
hostSetElementText(el, staticChildren);
} else {
hostInsertStaticContent(el, staticChildren, anchor, namespace);
}
}
- 静态内容字符串化
- 减少 VNode 创建
- 优化内存占用
- 提升渲染性能
4. 内存优化
4.1 对象池复用
// VNode 复用
// packages/runtime-core/src/vnode.ts
// 调用链: cloneVNode -> createVNode
const vnode = cloneVNode(cachedVNode, {
el: cachedVNode.el,
anchor: cachedVNode.anchor,
});
// 事件处理函数复用
// packages/runtime-dom/src/modules/events.ts
// 调用链: patchProp -> patchEvent
if (nextValue !== oldValue) {
el._vei = nextValue;
}
- VNode 对象复用
- 事件处理函数复用
- 减少垃圾回收
- 内存使用优化
4.2 Fragment 优化
// 使用 Fragment 减少节点层级
// packages/runtime-core/src/vnode.ts
// 调用链: createVNode -> createBlock
const Fragment = Symbol("Fragment");
const vnode = createVNode(Fragment, null, children);
- 减少 DOM 节点
- 优化节点层级
- 提升渲染性能
- 降低内存占用
5. 工具链优化
5.1 Vite 开发优化
// 开发环境按需编译
// packages/runtime-core/src/warning.ts
// 调用链: warn -> callWithErrorHandling
if (__DEV__) {
warn(`Invalid vnode type`);
}
// HMR 支持
// packages/@vue/runtime-core/src/hmr.ts
// 调用链: registerHMR -> rerender
if (import.meta.hot) {
// 热更新处理
}
- 按需编译
- 快速热更新
- 开发体验优化
- 构建性能提升
5.2 打包优化
// 代码分割
const AsyncComp = defineAsyncComponent(
() => import("./components/AsyncComp.vue")
);
// 动态导入
const routes = [
{
path: "/foo",
component: () => import("./Foo.vue"),
},
];
- 代码分割
- 动态导入
- 懒加载优化
- 缓存策略
5.3 预编译优化
// 静态提升
// packages/compiler-core/src/transforms/hoistStatic.ts
// 调用链: transform -> hoistStatic -> getConstantType
const hoisted = createVNode("div", null, "Static");
// 事件缓存
// packages/runtime-dom/src/modules/events.ts
// 调用链: patchProp -> patchEvent -> cacheHandler
const cache = new WeakMap();
const handler = cache.get(fn) || cache.set(fn, (e) => fn(e)).get(fn);
// v-once 优化
// packages/compiler-core/src/transforms/vOnce.ts
// 调用链: transform -> transformOnce
if (vOnce) {
vnode.patchFlag |= PatchFlags.HOISTED;
}
- 模板预编译
- 事件处理函数缓存
- v-once 静态标记
- 动态节点收集
5.4 Diff 算法优化
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch
// 1. 快速路径判断
const patch: PatchFn = (n1, n2, container, anchor = null) => {
// 类型不同,直接卸载旧节点
if (n1 && !isSameVNodeType(n1, n2)) {
unmount(n1);
n1 = null;
}
// 特殊 flags 处理
const { type, shapeFlag } = n2;
switch (type) {
case Text:
// 文本节点快速处理
processText(n1, n2, container);
break;
case Comment:
// 注释节点快速处理
processCommentNode(n1, n2, container);
break;
case Fragment:
// Fragment 快速处理
processFragment(n1, n2, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// 元素节点优化处理
processElement(n1, n2, container);
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 组件节点优化处理
processComponent(n1, n2, container);
}
}
};
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processElement
// 2. 元素节点 diff 优化
const processElement = (n1, n2, container) => {
if (n1 == null) {
// 挂载优化
mountElement(n2, container);
} else {
// 更新优化
patchElement(n1, n2);
}
};
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> processElement -> patchElement
// 3. 属性更新优化
const patchElement = (n1, n2) => {
const el = (n2.el = n1.el);
const oldProps = n1.props || EMPTY_OBJ;
const newProps = n2.props || EMPTY_OBJ;
// 动态子节点优化
if (n2.dynamicChildren) {
// 只更新动态节点
patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren);
} else if (!n2.optimized) {
// 全量 diff
patchChildren(n1, n2, el);
}
// 根据 patchFlag 按需更新属性
if (n2.patchFlag > 0) {
if (n2.patchFlag & PatchFlags.FULL_PROPS) {
// 需要完整 diff 的属性
patchProps(el, n2, oldProps, newProps);
} else {
// 按位处理动态属性
if (n2.patchFlag & PatchFlags.CLASS) {
if (oldProps.class !== newProps.class) {
hostPatchProp(el, "class", null, newProps.class);
}
}
if (n2.patchFlag & PatchFlags.STYLE) {
hostPatchProp(el, "style", oldProps.style, newProps.style);
}
// 处理动态 props
if (n2.patchFlag & PatchFlags.PROPS) {
const propsToUpdate = n2.dynamicProps;
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i];
const prev = oldProps[key];
const next = newProps[key];
if (prev !== next) {
hostPatchProp(el, key, prev, next);
}
}
}
}
}
};
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> patchChildren -> patchKeyedChildren
// 4. 双端 Diff 算法
const patchKeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
parentAnchor: RendererNode | null
) => {
let i = 0; // 头部索引
const l2 = c2.length; // 新子节点长度
let e1 = c1.length - 1; // 旧子节点尾部索引
let e2 = l2 - 1; // 新子节点尾部索引
// 1. 从头部开始同步
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container);
} else {
break;
}
i++;
}
// 2. 从尾部开始同步
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container);
} else {
break;
}
e1--;
e2--;
}
};
// packages/runtime-core/src/renderer.ts
// 调用链: createRenderer -> baseCreateRenderer -> patch -> patchKeyedChildren -> getSequence
// 5. 最长递增子序列算法优化移动
function getSequence(arr: number[]): number[] {
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {
j = result[result.length - 1];
if (arr[j] < arrI) {
p[i] = j;
result.push(i);
continue;
}
u = 0;
v = result.length - 1;
while (u < v) {
c = (u + v) >> 1;
if (arr[result[c]] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1];
}
result[u] = i;
}
}
}
u = result.length;
v = result[u - 1];
while (u-- > 0) {
result[u] = v;
v = p[v];
}
return result;
}
patch 函数的主要优化手段:
-
快速路径判断:
- 类型不同直接卸载重建
- 特殊节点类型快速处理
- 组件和元素分开处理
-
元素更新优化:
- 挂载和更新分开处理
- 复用 DOM 节点
- 属性按需更新
-
属性 diff 优化:
- PatchFlag 指导更新
- 动态属性快速定位
- 静态属性跳过比对
- 按位标记处理属性
-
双端 Diff 优化:
- 同时从两端开始比对,减少比对次数
- 处理节点位置交换的常见场景
- 优化节点的插入和移动操作
- 适用于节点位置颠倒的情况
-
最长递增子序列优化:
- 计算稳定序列,最小化节点移动
- 使用二分查找提高计算效率
- 优化批量节点更新的性能
- 减少 DOM 操作的次数
这些优化手段结合使用,显著提升了 Vue3 的更新性能:
-
对于静态内容:
- 直接跳过 diff
- 复用已有节点
-
对于动态属性:
- 精确定位更新
- 避免全量对比
-
对于节点移动:
- 双端比对减少移动
- 最长子序列优化排序
-
对于大规模更新:
- 算法优化提升效率
- 最小化 DOM 操作
5.5 编译器优化
// packages/compiler-core/src/transform.ts
// 调用链: transform -> createVNodeCall -> processIf
function transformIf(node, context) {
if (node.type === NodeTypes.IF) {
// 将连续的 v-if/v-else-if/v-else 转换为 switch 结构
return processIf(node, context, true);
}
}
- 表达式缓存优化
- 动态节点收集优化
- 条件渲染优化
- 循环渲染优化
6. 组件优化
6.1 KeepAlive 缓存
// 组件缓存
<keep-alive :include="['a', 'b']">
<component :is="view"/>
</keep-alive>
// 缓存实现
// packages/runtime-core/src/components/KeepAlive.ts
// 调用链: KeepAlive -> cacheSubtree
if (cachedVNode) {
vnode.el = cachedVNode.el;
vnode.component = cachedVNode.component;
}
- 组件状态缓存
- DOM 复用
- 性能优化
- 用户体验提升
6.2 异步组件
// 异步组件
// packages/runtime-core/src/apiAsyncComponent.ts
// 调用链: defineAsyncComponent -> createAsyncComponent
const AsyncComp = defineAsyncComponent({
loader: () => import("./Comp.vue"),
loadingComponent: LoadingComp,
delay: 200,
timeout: 3000,
});
- 按需加载
- 按需加载
- 加载状态控制
- 错误处理
- 超时控制
7. 总结
Vue3 的性能优化是全方位的:
-
底层优化:
- PatchFlag 动态节点标记
- Block Tree 优化更新
- 响应式系统重写
- 静态内容提升
- Effect 调度系统优化
- Diff 算法优化
- 调度系统优化
- 编译器表达式缓存
- Patch 函数内部优化
- 双端 Diff 算法优化
- 最长递增子序列优化
-
工具链优化:
- SSR 优化和水合
- Tree-shaking 支持
- Vite 开发体验
- 打包策略优化
-
开发体验优化:
- 对象池复用机制
- Fragment 减少节点
- 组件缓存复用
- 异步组件加载
-
运行时优化:
- 更小的体积
- 更好的性能
- 更强的扩展性
- 更完善的 SSR
这些优化措施从编译时到运行时,从底层到工具链,构建了一个全方位的性能优化体系。使得 Vue3 不仅在性能上有了质的飞跃,在开发体验、工程化等方面也都有了显著提升。特别是在大型应用中,这些优化带来的收益更为明显。