首页  |  学习总览  |  ← 返回专题总览 进阶专题 10 · 源码解析

Vue3 源码深度解析 🔥 重点

响应式系统、编译优化、虚拟 DOM 与 Diff 算法,彻底搞懂 Vue3 运行机制
学习时长:约 16 小时 | 前置:阶段 05 框架核心 | 产出:手写 mini-Vue + 源码阅读笔记

一、Vue3 架构总览

Vue3 的模块化架构

@vue/reactivity
+
@vue/runtime-core
+
@vue/runtime-dom
+
@vue/compiler-core
+
@vue/compiler-dom
+
@vue/compiler-sfc
模块职责可独立使用
@vue/reactivity响应式系统(ref/reactive/effect)✅ 是
@vue/runtime-core平台无关运行时(渲染器、组件)✅ 是
@vue/runtime-domDOM 渲染实现❌ 否
@vue/compiler-core模板编译器核心✅ 是
@vue/compiler-domDOM 平台编译优化❌ 否
@vue/compiler-sfc单文件组件编译❌ 否
核心优势:Vue3 的模块化设计让每个包都可以独立使用,比如 @vue/reactivity 可以直接在 Node.js 或其他框架中使用。

二、响应式系统原理

2.1 Proxy 代理

Vue3 使用 Proxy 替代 Vue2 的 Object.defineProperty

// reactive 的实现原理
function reactive(target) {
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  // 已经是 Proxy 则直接返回
  if (target[ReactiveFlags.IS_REACTIVE]) {
    return target;
  }
  
  return new Proxy(target, mutableHandlers);
}

// 基础处理器
const mutableHandlers = {
  get(target, key, receiver) {
    if (key === ReactiveFlags.IS_REACTIVE) return true;
    track(target, key); // 依赖收集
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);
    if (hasChanged(value, oldValue)) {
      trigger(target, key); // 触发更新
    }
    return result;
  }
};

2.2 依赖收集(track)

// 嵌套的 WeakMap 结构
// targetMap: { target -> { key -> Set } }
const targetMap = new WeakMap();

let activeEffect = null;
let shouldTrack = false;

function isTracking() {
  return shouldTrack && activeEffect !== null;
}

function track(target, key) {
  if (!isTracking()) return;
  
  // 获取 target 对应的 depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取 key 对应的 dep(Set)
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  
  // 添加当前 activeEffect
  trackEffects(dep);
}

function trackEffects(dep) {
  if (activeEffect && !dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}

2.3 触发更新(trigger)

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return; // 没有被依赖的属性
  
  const dep = depsMap.get(key);
  if (!dep) return;
  
  triggerEffects(dep);
}

function triggerEffects(dep) {
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler(); // 调度执行(组件更新走 scheduler)
    } else {
      effect.run(); // 直接执行
    }
  }
}

2.4 effect / ReactiveEffect

// ReactiveEffect 类(核心!)
class ReactiveEffect {
  active = true;
  deps = [];
  parent = undefined;
  
  constructor(fn, scheduler) {
    this.fn = fn;
    this.scheduler = scheduler;
  }
  
  run() {
    if (!this.active) return this.fn();
    
    this.parent = activeEffect;
    activeEffect = this;
    shouldTrack = true;
    
    try {
      return this.fn(); // 执行时会触发 get,进行依赖收集
    } finally {
      activeEffect = this.parent;
      this.parent = undefined;
      shouldTrack = false;
    }
  }
  
  stop() {
    if (this.active) {
      cleanupEffect(this);
      this.active = false;
    }
  }
}

// effect 函数
function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn);
  
  if (!options.lazy) {
    _effect.run();
  }
  return _effect.run.bind(_effect);
}

2.5 ref 的实现

// ref 本质上是一个带有 value 访问器的对象
class RefImpl {
  _value;
  _rawValue;
  dep = new Set();
  
  constructor(value) {
    this._rawValue = value;
    this._value = toReactive(value);
  }
  
  get value() {
    trackRefValue(this); // 依赖收集
    return this._value;
  }
  
  set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal;
      this._value = toReactive(newVal);
      triggerEffects(this.dep); // 触发更新
    }
  }
}

function ref(value) {
  return new RefImpl(value);
}

// ref vs reactive 对比
// ref: 包装原始值,通过 .value 访问,适合基本类型
// reactive: 代理对象,直接访问属性,适合对象类型
注意:reactive 不能解构!解构后会失去响应式。如果需要解构,使用 toRefs

三、编译优化(Compiler Optimizations)

3.1 Block Tree 与 PatchFlag

Vue3 编译器会在编译阶段分析模板,标记动态节点:

// 编译前的模板
<template>
  <div class="static">Hello</div>
  <div :class="dynamicClass">World</div>
  <span>{{ message }}</span>
</template>

/// 编译后的渲染函数(简化)
function render() {
  return (openBlock(), createElementBlock("div", null, [
    createElementVNode("div", { class: "static" }, "Hello"),
    createElementVNode("div", { class: _ctx.dynamicClass }, "World", 2 /* CLASS */),
    createElementVNode("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */),
  ]));
}

PatchFlag 类型

Flag含义
TEXT1动态文本
CLASS2动态类名
STYLE4动态样式
PROPS8动态属性
FULL_PROPS16动态 key
HYDRATE_EVENTS32事件监听
STABLE_FRAGMENT64稳定 Fragment
KEYED_FRAGMENT128带 key 的 Fragment
UNKEYED_FRAGMENT256无 key 的 Fragment
NEED_PATCH512需要 patch
DYNAMIC_SLOTS1024动态插槽

3.2 静态提升(HoistStatic)

将静态节点提升到渲染函数外部,避免重复创建:

// 编译前
<template>
  <div>
    <div>Static 1</div>
    <div>Static 2</div>
    <div>{{ dynamic }}</div>
  </div>
</template>

// 编译后:静态节点被提升为常量
const _hoisted_1 = /*#__PURE__*/ createElementVNode("div", null, "Static 1");
const _hoisted_2 = /*#__PURE__*/ createElementVNode("div", null, "Static 2");

function render() {
  return (openBlock(), createElementBlock("div", null, [
    _hoisted_1,   // 复用
    _hoisted_2,   // 复用
    createElementVNode("div", null, _toDisplayString(_ctx.dynamic), 1),
  ]));
}

3.3 预字符串化(Static Hoisting)

// 当静态节点数量很大时(默认 50 个),直接生成 innerHTML
function render() {
  return (openBlock(), createElementBlock("div", {
    innerHTML: _hoisted_1  // 预字符串化的静态内容
  }, null, 8 /* PROPS */, ["innerHTML"]));
}

3.4 缓存事件处理函数(CacheHandlers)

// 编译前
<template>
  <button @click="() => count++">Click</button>
</template>

// 编译后:内联箭头函数会被缓存
function render() {
  return (openBlock(), createElementBlock("button", {
    onClick: _cache[0] || (_cache[0] = ($event) => (_ctx.count++))
  }, "Click"));
}
编译优化效果:Vue3 相比 Vue2,更新性能提升约 1.3~2 倍,内存占用减少约 50%。

四、虚拟 DOM 与 Diff 算法

4.1 VNode 结构

// 虚拟 DOM 节点结构
const vnode = {
  __v_isVNode: true,
  type: 'div',           // 标签类型
  props: { id: 'app' },   // 属性
  key: null,                 // diff 标识
  children: [                // 子节点
    { type: 'span', children: 'Hello' }
  ],
  shapeFlag: 11,             // 形状标记(位运算)
  el: null,                   // 真实 DOM 引用
  component: null,             // 组件实例引用
};

// ShapeFlags(位运算标记)
export const enum ShapeFlags {
  ELEMENT = 1,                    // 0001
  FUNCTIONAL_COMPONENT = 1 << 1,  // 0010
  STATEFUL_COMPONENT = 1 << 2,   // 0100
  TEXT_CHILDREN = 1 << 3,       // 1000
  ARRAY_CHILDREN = 1 << 4,
  SLOTS_CHILDREN = 1 << 5,
  TELEPORT = 1 << 6,
  SUSPENSE = 1 << 7,
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
  COMPONENT_KEPT_ALIVE = 1 << 9,
  COMPONENT = STATEFUL_COMPONENT | FUNCTIONAL_COMPONENT
}

4.2 Diff 算法核心

Vue3 的 Diff 算法采用双端对比策略:

// 简化的 Diff 算法
function patchKeyedChildren(c1, c2, container) {
  let i = 0;              // 新旧的起始索引
  let e1 = c1.length - 1; // 旧的结束索引
  let e2 = c2.length - 1; // 新的结束索引
  
  // 1. 从左到右扫描
  while (i <= e1 && i <= e2) {
    const n1 = c1[i];
    const n2 = c2[i];
    if (isSameVNodeType(n1, n2)) {
      patch(n1, n2, container); // 递归 patch
    } 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--;
  }
  
  // 3. 处理剩余节点(新增、删除、移动)
  if (i > e1) {
    // 新节点更多 → 新增
    const nextPos = e2 + 1;
    const anchor = nextPos < c2.length ? c2[nextPos].el : null;
    while (i <= e2) {
      patch(null, c2[i++], container, anchor);
    }
  } else if (i > e2) {
    // 旧节点更多 → 删除
    while (i <= e1) {
      unmount(c1[i++], parentComponent, parentSuspense, true);
    }
  } else {
    // 4. 复杂情况:移动和复用
    // 使用 key 的 Map 进行最长递增子序列优化
    ... 后续代码省略
  }
}

Vue2 的 Diff

  • 双端对比 + 暴力比对
  • O(n²) 最坏情况
  • 只能同级比较

Vue3 的 Diff

  • 双端对比 + 最长递增子序列
  • O(n log n) 最优
  • 利用 PatchFlag 跳过静态节点

五、Composition API 原理

5.1 setup 函数

// setup 在组件创建前执行,返回渲染函数或数据对象
function setup(props, { emit, slots, attrs, expose }) {
  // 响应式数据
  const count = ref(0);
  const state = reactive({ name: 'Vue' });
  
  // 计算属性
  const double = computed(() => count.value * 2);
  
  // 监听器
  watch(count, (newVal) => {
    console.log('count changed:', newVal);
  });
  
  // 生命周期钩子
  onMounted(() => {
    console.log('mounted');
  });
  
  // 返回的数据可以在模板中使用
  return { count, state, double };
}

5.2 computed 实现

// computed 本质上是一个带缓存的 effect
class ComputedRefImpl {
  _getter;
  _dirty = true;       // 脏检查标记
  _value;
  effect;
  
  constructor(getter) {
    // 创建一个 lazy effect
    this.effect = new ReactiveEffect(getter, () => {
      // 当依赖变化时,标记为 dirty
      if (!this._dirty) {
        this._dirty = true;
      }
    });
  }
  
  get value() {
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run(); // 重新计算
    }
    return this._value;
  }
}

5.3 watch 实现

// watch 的核心是创建一个 effect 并监听变化
function watch(source, cb, options = {}) {
  let getter;
  
  // 处理不同类型的数据源
  if (isRef(source)) {
    getter = () => source.value;
  } else if (isReactive(source)) {
    getter = () => traverse(source); // 递归访问所有属性
  } else if (isFunction(source)) {
    getter = source;
  }
  
  let oldValue;
  
  // 调度函数:变化时执行
  const job = () => {
    const newValue = effect.run();
    cb(newValue, oldValue); // 调用回调
    oldValue = newValue;
  };
  
  const effect = new ReactiveEffect(getter, job);
  
  if (options.immediate) {
    job(); // 立即执行
  } else {
    oldValue = effect.run(); // 初始化 oldValue
  }
}

六、组件渲染与更新流程

createApp()
app.mount()
createVNode()
render()
patch()
mountComponent()
setupComponent()
setupRenderEffect()
生成子树 VNode
patch 子节点
挂载完成

6.1 setupRenderEffect

// 组件渲染的核心 effect
function setupRenderEffect(instance, container, anchor) {
  const componentUpdateFn = () => {
    if (!instance.isMounted) {
      // 首次挂载
      const subTree = (instance.subTree = renderComponentRoot(instance));
      patch(null, subTree, container, anchor);
      instance.isMounted = true;
    } else {
      // 更新
      const nextTree = renderComponentRoot(instance);
      const prevTree = instance.subTree;
      instance.subTree = nextTree;
      patch(prevTree, nextTree, container, anchor);
    }
  };
  
  // 创建 ReactiveEffect,scheduler 加入更新队列
  const effect = (instance.effect = new ReactiveEffect(
    componentUpdateFn,
    () => queueJob(update)  // 异步批量更新
  ));
  
  const update = (instance.update = () => effect.run());
  update();
}

七、手写 mini-Vue 实战

7.1 实现 h 函数

function h(type, props, children) {
  return {
    type,
    props: props || {},
    children: typeof children === 'string' ? children : children?.map(child =>
      typeof child === 'string' ? { type: 'text', children: child } : child
    ),
  };
}

// 使用示例
h('div', { id: 'app' }, [
  h('span', null, 'Hello'),
  'World'
]);

7.2 实现 render 函数

function render(vnode, container) {
  if (typeof vnode.type === 'string') {
    mountElement(vnode, container);
  } else {
    mountComponent(vnode, container);
  }
}

function mountElement(vnode, container) {
  const el = document.createElement(vnode.type);
  
  // 处理 props
  for (const [key, val] of Object.entries(vnode.props)) {
    el.setAttribute(key, val);
  }
  
  // 处理 children
  if (typeof vnode.children === 'string') {
    el.textContent = vnode.children;
  } else {
    vnode.children.forEach(child => render(child, el));
  }
  
  container.appendChild(el);
  vnode.el = el;
}

7.3 实现响应式系统

let activeEffect = null;

class ReactiveEffect {
  constructor(fn) { this.fn = fn; }
  run() {
    activeEffect = this;
    return this.fn();
  }
}

const targetMap = new WeakMap();

function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key);
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      trigger(target, key);
      return true;
    }
  });
}

function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) targetMap.set(target, (depsMap = new Map()));
  let dep = depsMap.get(key);
  if (!dep) depsMap.set(key, (dep = new Set()));
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  dep?.forEach(effect => effect.run());
}

function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

八、面试高频问题

Q1: Vue3 为什么用 Proxy 替代 Object.defineProperty?
查看答案 ▼
  • 检测属性删除:defineProperty 无法检测 delete obj.key,Proxy 可以
  • 检测数组变化:defineProperty 无法检测 arr[index] = val,需要特殊处理
  • 性能更好:Proxy 是懒代理,只在访问时拦截;defineProperty 需要递归遍历所有属性
  • 支持更多拦截操作:has、ownKeys、deleteProperty 等
Q2: ref 和 reactive 有什么区别?
查看答案 ▼
  • ref:包装原始值,通过 .value 访问,适合基本类型和需要替换整个对象的场景
  • reactive:代理对象,直接访问属性,适合复杂对象,但不能解构

实际开发中,推荐用 ref 作为默认选择,因为更灵活。

Q3: Vue3 的编译优化有哪些?
查看答案 ▼
  • PatchFlag:标记动态节点,diff 时只比较动态部分
  • 静态提升:静态节点提升到渲染函数外部,避免重复创建
  • 预字符串化:大量静态节点直接生成 innerHTML
  • 缓存事件:内联事件处理函数缓存,避免重复创建
Q4: nextTick 的实现原理?
查看答案 ▼
Vue 的 DOM 更新是异步批量的。nextTick 的实现:
  1. 将回调加入微任务队列
  2. 优先使用 Promise.then
  3. 降级方案:MessageChannelsetTimeout
所以 nextTick 回调会在 DOM 更新完成后执行。