vue3源码解析:性能优化总结

107 阅读10分钟

通过前面的文章,我们已经分析了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. 运行时按需更新
  3. 避免全量对比
  4. 精确定位变化

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. 收集动态节点
  2. 跳过静态节点
  3. 减少比对范围
  4. 提升更新性能

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. 同构渲染
  2. 静态标记
  3. 客户端激活
  4. 选择性水合

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 优化的几个层面:

  1. API 级别: Vue2:

    • 所有 API 都挂载在 Vue 实例上
    • 无法进行 Tree-shaking
    • 即使不用某些功能也会打包
    • 导致包体积较大

    Vue3:

    • 支持按需导入 API
    • 未使用的 API 不会打包
    • 减小最终包体积
    • 例如:只使用 ref 不使用 reactive
  2. 编译优化: Vue2:

    • 所有编译功能打包在一起
    • 无法剔除未使用的编译器代码
    • 运行时包含冗余功能
    • 包体积优化空间小

    Vue3:

    • 编译时分析模板依赖
    • 只引入使用到的运行时代码
    • 移除未使用的模块
    • 例如:没有使用过渡动画,transition 模块不会打包
  3. 特性标记: Vue2:

    • 无特性标记机制
    • 所有特性代码都会打包
    • 无法按需加载功能
    • 冗余代码较多

    Vue3:

    • 编译时标记使用的功能
    • 运行时按需加载功能模块
    • 自动剔除未使用特性
    • 例如:没有使用 v-model,相关代码不会打包
  4. 优化效果: 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);
  1. 避免递归更新
  2. 精确依赖收集
  3. 异步更新队列
  4. 防止重复渲染

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;
}
  1. 事件处理函数缓存
  2. VNode 复用
  3. DOM 节点缓存
  4. 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();
}
  1. 异步渲染队列
  2. 批量更新策略
  3. 渲染任务调度
  4. 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);
  }
};
  1. 任务优先级排序
  2. 微任务批量处理
  3. 更新任务去重
  4. 渲染任务合并

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);
  }
}
  1. 静态节点提升
  2. 静态树提升
  3. 减少创建开销
  4. 内存占用优化

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);
  }
}
  1. 静态内容字符串化
  2. 减少 VNode 创建
  3. 优化内存占用
  4. 提升渲染性能

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;
}
  1. VNode 对象复用
  2. 事件处理函数复用
  3. 减少垃圾回收
  4. 内存使用优化

4.2 Fragment 优化

// 使用 Fragment 减少节点层级
// packages/runtime-core/src/vnode.ts
// 调用链: createVNode -> createBlock
const Fragment = Symbol("Fragment");
const vnode = createVNode(Fragment, null, children);
  1. 减少 DOM 节点
  2. 优化节点层级
  3. 提升渲染性能
  4. 降低内存占用

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) {
  // 热更新处理
}
  1. 按需编译
  2. 快速热更新
  3. 开发体验优化
  4. 构建性能提升

5.2 打包优化

// 代码分割
const AsyncComp = defineAsyncComponent(
  () => import("./components/AsyncComp.vue")
);

// 动态导入
const routes = [
  {
    path: "/foo",
    component: () => import("./Foo.vue"),
  },
];
  1. 代码分割
  2. 动态导入
  3. 懒加载优化
  4. 缓存策略

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;
}
  1. 模板预编译
  2. 事件处理函数缓存
  3. v-once 静态标记
  4. 动态节点收集

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 函数的主要优化手段:

  1. 快速路径判断:

    • 类型不同直接卸载重建
    • 特殊节点类型快速处理
    • 组件和元素分开处理
  2. 元素更新优化:

    • 挂载和更新分开处理
    • 复用 DOM 节点
    • 属性按需更新
  3. 属性 diff 优化:

    • PatchFlag 指导更新
    • 动态属性快速定位
    • 静态属性跳过比对
    • 按位标记处理属性
  4. 双端 Diff 优化:

    • 同时从两端开始比对,减少比对次数
    • 处理节点位置交换的常见场景
    • 优化节点的插入和移动操作
    • 适用于节点位置颠倒的情况
  5. 最长递增子序列优化:

    • 计算稳定序列,最小化节点移动
    • 使用二分查找提高计算效率
    • 优化批量节点更新的性能
    • 减少 DOM 操作的次数

这些优化手段结合使用,显著提升了 Vue3 的更新性能:

  1. 对于静态内容:

    • 直接跳过 diff
    • 复用已有节点
  2. 对于动态属性:

    • 精确定位更新
    • 避免全量对比
  3. 对于节点移动:

    • 双端比对减少移动
    • 最长子序列优化排序
  4. 对于大规模更新:

    • 算法优化提升效率
    • 最小化 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);
  }
}
  1. 表达式缓存优化
  2. 动态节点收集优化
  3. 条件渲染优化
  4. 循环渲染优化

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;
}
  1. 组件状态缓存
  2. DOM 复用
  3. 性能优化
  4. 用户体验提升

6.2 异步组件

// 异步组件
// packages/runtime-core/src/apiAsyncComponent.ts
// 调用链: defineAsyncComponent -> createAsyncComponent
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  loadingComponent: LoadingComp,
  delay: 200,
  timeout: 3000,
});
  1. 按需加载
  2. 按需加载
  3. 加载状态控制
  4. 错误处理
  5. 超时控制

7. 总结

Vue3 的性能优化是全方位的:

  1. 底层优化:

    • PatchFlag 动态节点标记
    • Block Tree 优化更新
    • 响应式系统重写
    • 静态内容提升
    • Effect 调度系统优化
    • Diff 算法优化
    • 调度系统优化
    • 编译器表达式缓存
    • Patch 函数内部优化
    • 双端 Diff 算法优化
    • 最长递增子序列优化
  2. 工具链优化:

    • SSR 优化和水合
    • Tree-shaking 支持
    • Vite 开发体验
    • 打包策略优化
  3. 开发体验优化:

    • 对象池复用机制
    • Fragment 减少节点
    • 组件缓存复用
    • 异步组件加载
  4. 运行时优化:

    • 更小的体积
    • 更好的性能
    • 更强的扩展性
    • 更完善的 SSR

这些优化措施从编译时到运行时,从底层到工具链,构建了一个全方位的性能优化体系。使得 Vue3 不仅在性能上有了质的飞跃,在开发体验、工程化等方面也都有了显著提升。特别是在大型应用中,这些优化带来的收益更为明显。