Vue实现核心原理

目录

    • [1 前言](#1 前言)
    • 2.核心原理解析
      • [2.1核心原理 1:响应式原理(数据驱动视图的基石)](#2.1核心原理 1:响应式原理(数据驱动视图的基石))
      • [2.2 核心原理 2:虚拟 DOM & Diff 算法(高效更新视图)](#2.2 核心原理 2:虚拟 DOM & Diff 算法(高效更新视图))
      • [2.3 核心原理 3:模板编译原理(连接模板和渲染)](#2.3 核心原理 3:模板编译原理(连接模板和渲染))
        • 核心流程(三步曲)
        • [Vue2 vs Vue3 编译差异](#Vue2 vs Vue3 编译差异)
          • [1. 解析阶段(Parse):AST 结构与新特性支持](#1. 解析阶段(Parse):AST 结构与新特性支持)
          • [2. 优化阶段(Optimize):静态节点处理(核心差异)](#2. 优化阶段(Optimize):静态节点处理(核心差异))
            • [Vue2 优化阶段:基础静态标记](#Vue2 优化阶段:基础静态标记)
            • [Vue3 优化阶段:静态提升 + 动态标记(核心优化)](#Vue3 优化阶段:静态提升 + 动态标记(核心优化))
            • [(1)静态提升(Static Hoisting)](#(1)静态提升(Static Hoisting))
            • (2)动态节点精准标记
          • [3. 生成阶段(Generate):render 函数产物差异](#3. 生成阶段(Generate):render 函数产物差异)
          • 关键优化:PatchFlags(补丁标记)
        • [Vue2 vs Vue3 模板编译核心差异表](#Vue2 vs Vue3 模板编译核心差异表)
        • 实战示例:编译产物对比
          • 模板
          • [Vue2 编译后的 render 函数](#Vue2 编译后的 render 函数)
          • [Vue3 编译后的 render 函数](#Vue3 编译后的 render 函数)
      • [2.4 核心原理 4:组件化原理(代码复用的核心)](#2.4 核心原理 4:组件化原理(代码复用的核心))
        • 核心逻辑
        • [Vue2 vs Vue3 组件化核心差异表](#Vue2 vs Vue3 组件化核心差异表)
    • [3 总结](#3 总结)

1 前言

Vue 的的设计围绕数据驱动视图组件化开发两大核心目标展开,所有设计都是为了让开发者无需手动操作 DOM,只需关注数据和业务逻辑,同时实现代码的高效复用。其核心原理包括如下四个:

  • 响应式
  • 虚拟dom和diff算法
  • 模板编译
  • 组件化

以下从源码进行原理解析。

2.核心原理解析

2.1核心原理 1:响应式原理(数据驱动视图的基石)

解决的问题:如何让数据变化 自动触发视图更新

这是 Vue 最核心的能力。把数据变成可监听的状态,当你修改数据时,Vue 能"感知"到变化,并自动通知依赖这个数据的视图部分重新渲染。

核心实现(Vue2 vs Vue3)
版本 核心技术 核心逻辑 优缺点
Vue2 Object.defineProperty 遍历对象的每一个属性,重写属性的 get(读取时收集依赖)和 set(修改时触发更新)方法;对数组则重写 push/pop 等 7 个方法实现监听 ✅ 兼容性好;❌ 只能监听已有属性,新增/删除属性需用 $set/$delete,数组索引修改无法监听
Vue3 Proxy + Reflect Proxy 包裹整个对象(而非单个属性),拦截对象的所有操作(读取、修改、新增、删除);Reflect 统一处理操作结果 ✅ 监听范围全覆盖(新增属性、数组任意修改都支持);✅ 懒监听(访问时才递归监听嵌套对象),性能更优;❌ 不兼容 IE
核心流程(一句话)

读取数据 → 收集"谁用到了这个数据"(依赖收集)→ 修改数据 → 通知"用到的地方"更新(触发更新)。

源码

以下提供 Vue2 官方源码(Flow 语法)Vue3 官方源码(TypeScript 语法) 中响应式实现的核心文件代码(非简化版,完全对齐官方仓库),并标注代码来源路径,你可直接对照官方仓库验证。

Vue2 响应式核心源码(官方原版)

Vue2 源码仓库:https://github.com/vuejs/vue

响应式核心集中在 src/core/observer/ 目录下,以下是核心文件的完整源码(关键部分标注注释)。

Dep.js(依赖收集器)

路径:src/core/observer/dep.js

javascript 复制代码
/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
 * 依赖收集器:每个响应式属性对应一个Dep实例,管理该属性的所有Watcher
 */
export default class Dep {
  static target: ?Watcher; // 静态属性:当前活跃的Watcher
  id: number;
  subs: Array<Watcher>; // 存储依赖当前属性的Watcher数组

  constructor () {
    this.id = uid++
    this.subs = []
  }

  /**
   * 添加Watcher到依赖列表
   */
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /**
   * 从依赖列表移除Watcher
   */
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  /**
   * 收集依赖:只有存在活跃Watcher时才收集
   */
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /**
   * 通知所有Watcher更新
   */
  notify () {
    // 浅拷贝依赖列表,避免更新过程中列表变化
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // 非生产环境且同步模式:按id排序,保证更新顺序
      subs.sort((a, b) => a.id - b.id)
    }
    // 遍历执行所有Watcher的update方法
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// 初始化Dep.target为null(当前无活跃Watcher)
Dep.target = null
const targetStack = []

/**
 * 入栈并设置当前Watcher
 */
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

/**
 * 出栈并恢复上一个Watcher
 */
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
Watcher.js(观察者)

路径:src/core/observer/watcher.js(核心片段,保留关键逻辑)

javascript 复制代码
/* @flow */

import { warn, remove, isObject } from '../util/index'
import { parsePath } from './path'
import Dep, { pushTarget, popTarget } from './dep'
import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * 观察者:连接数据和视图更新,分为渲染Watcher、计算属性Watcher、用户Watcher
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // 初始化配置
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // 唯一标识
    this.active = true
    this.dirty = this.lazy // 懒更新标记(计算属性用)
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    
    // 解析getter函数:如果是字符串(如'user.name'),解析为取值函数;否则直接用传入的函数(如渲染函数)
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    
    // 懒更新(计算属性):不立即执行get;否则执行get获取初始值并收集依赖
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * 核心:执行getter,收集依赖,并返回值
   */
  get () {
    pushTarget(this) // 将当前Watcher设为活跃状态
    let value
    const vm = this.vm
    try {
      // 执行getter(如渲染函数、取值函数),触发数据的getter,完成依赖收集
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // 深度监听:递归遍历对象,触发所有子属性的getter,收集依赖
      if (this.deep) {
        traverse(value)
      }
      popTarget() // 恢复上一个Watcher
      this.cleanupDeps() // 清理无用依赖
    }
    return value
  }

  /**
   * 添加Dep到依赖列表(去重)
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * 清理无用依赖(新依赖列表中不存在的Dep)
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    // 交换新/旧依赖列表,重置新列表
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * 更新逻辑:根据配置选择同步/异步更新
   */
  update () {
    if (this.lazy) {
      // 懒更新(计算属性):标记为脏,等待取值时更新
      this.dirty = true
    } else if (this.sync) {
      // 同步更新:立即执行run
      this.run()
    } else {
      // 异步更新:加入队列(Vue的异步更新队列),批量执行
      queueWatcher(this)
    }
  }

  /**
   * 执行更新回调(核心)
   */
  run () {
    if (this.active) {
      const value = this.get() // 重新获取值
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        // 保存旧值
        const oldValue = this.value
        this.value = value
        // 用户Watcher(vm.$watch):捕获错误
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          // 渲染Watcher/计算属性:执行回调
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * 求值(计算属性用)
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * 重新收集所有依赖
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * 销毁Watcher:移除所有依赖
   */
  teardown () {
    if (this.active) {
      // 从vm的Watcher列表中移除
      remove(this.vm._watchers, this)
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
defineReactive(响应式核心)

路径:src/core/observer/index.js(核心函数)

javascript 复制代码
/* @flow */

import { warn, isDef, isUndef, isPlainObject, isPrimitive, isArray } from '../util/index'
import { hasProto, protoAugment, copyAugment } from './array'
import Dep from './dep'
import type { SimpleSet } from '../util/index'

/**
 * 给对象的属性添加响应式(Vue2响应式的核心)
 * @param {Object} obj 目标对象
 * @param {string} key 属性名
 * @param {any} val 属性值
 * @param {?Function} customSetter 自定义setter
 * @param {boolean} shallow 是否浅监听(不递归子对象)
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 每个属性对应一个Dep实例
  const dep = new Dep()

  // 获取属性的原有描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return // 不可配置的属性不处理
  }

  // 缓存原有get/set
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 递归创建子对象的响应式(非浅监听时)
  let childOb = !shallow && observe(val)
  
  // 重写属性的get/set
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 执行原有getter(若有)
      const value = getter ? getter.call(obj) : val
      // 存在活跃Watcher时,收集依赖
      if (Dep.target) {
        dep.depend()
        // 子对象的Observer实例也收集依赖(用于数组/嵌套对象的更新)
        if (childOb) {
          childOb.dep.depend()
          // 数组:递归收集数组元素的依赖(深度监听)
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 执行原有getter获取旧值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 新旧值相同(或都是NaN),不更新
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // 原有setter不可写,直接返回
      if (getter && !setter) return
      // 执行原有setter(若有)
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新值创建响应式(非浅监听时)
      childOb = !shallow && observe(newVal)
      // 通知所有Watcher更新
      dep.notify()
    }
  })
}

/**
 * 为数组元素收集依赖(深度监听)
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (isArray(e)) {
      dependArray(e)
    }
  }
}

/**
 * 为对象创建Observer实例(响应式入口)
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // 该对象作为vm.$data的次数

  constructor (value: any) {
    this.value = value
    this.dep = new Dep() // 数组/对象的Observer实例对应一个Dep
    this.vmCount = 0
    // 给对象添加__ob__属性,标记为已响应式(不可枚举)
    def(value, '__ob__', this)
    // 数组:重写原型方法(push/pop等)实现监听
    if (isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value) // 为数组元素创建响应式
    } else {
      // 对象:遍历所有属性,添加响应式
      this.walk(value)
    }
  }

  /**
   * 遍历对象的所有属性,添加响应式
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * 遍历数组,为每个元素创建响应式
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

/**
 * 尝试为值创建Observer实例(入口函数)
 */
export function observe (value: any, shallow?: boolean): Observer | void {
  // 非对象/是VNode,不处理
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 已存在__ob__(已响应式),直接返回
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 符合条件:创建Observer实例
    ob = new Observer(value)
  }
  return ob
}

Vue3 响应式核心源码(官方原版)

Vue3 源码仓库:https://github.com/vuejs/core

响应式核心集中在 packages/reactivity/src/ 目录下,以下是核心文件的完整源码(TypeScript 语法)。

effect.ts(副作用函数核心)

路径:packages/reactivity/src/effect.ts

typescript 复制代码
import { extend, isArray, isIntegerKey, isMap, isSet } from '@vue/shared'
import { TriggerOpTypes, TrackOpTypes } from './operations'
import {
  Dep,
  createDep,
  finalizeDepMarkers,
  initDepMarkers,
  newTracked,
  wasTracked
} from './dep'
import { ComputedRefImpl } from './computed'

export type EffectScheduler = (...args: any[]) => any

export type DebuggerEvent = {
  effect: ReactiveEffect
} & DebuggerEventExtraInfo

export type DebuggerEventExtraInfo = {
  target: object
  type: TrackOpTypes | TriggerOpTypes
  key: any
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean
  /**
   * @internal
   */
  private deferStop?: boolean

  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope | null
  ) {
    recordEffectScope(this, scope)
  }

  run() {
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        cleanupEffect(this)
      }
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }

  stop() {
    // stopped while running itself - defer the cleanup
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

function cleanupEffect(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

export interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

export interface EffectOptions extends DebuggerOptions {
  lazy?: boolean
  scheduler?: EffectScheduler
  scope?: EffectScope
  allowRecurse?: boolean
  onStop?: () => void
}

export interface ReactiveEffectRunner<T = any> {
  (): T
  effect: ReactiveEffect<T>
}

export function effect<T = any>(
  fn: () => T,
  options?: EffectOptions
): ReactiveEffectRunner {
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  const _effect = new ReactiveEffect(fn)
  if (options) {
    extend(_effect, options)
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

export function stop(runner: ReactiveEffectRunner) {
  runner.effect.stop()
}

export let shouldTrack = true
const trackStack: boolean[] = []

export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}

export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

// The number of effects currently being tracked recursively.
export let effectTrackDepth = 0
export let trackOpBit = 1

/**
 * The bitwise track markers support at most 30 levels of recursion.
 * This value is chosen to enable modern JS engines to use a SMI on all platforms.
 * When recursion depth is greater, fall back to using a cleanup approach.
 */
export const maxMarkerBits = 30

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    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 = createDep()))
  }

  const eventInfo = __DEV__
    ? { effect: activeEffect, target, type, key }
    : undefined

  trackEffects(dep, eventInfo)
}

export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && debuggerEventExtraInfo) {
      activeEffect!.onTrack?.(extend({ effect: activeEffect! }, debuggerEventExtraInfo))
    }
  }
}

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared, trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      triggerComputed(effect)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      if (__DEV__ && debuggerEventExtraInfo) {
        const event: DebuggerEvent = extend(
          { effect },
          debuggerEventExtraInfo
        )
        effect.onTrigger?.(event)
      }
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

function triggerComputed(effect: ReactiveEffect) {
  if (effect.dirty) {
    return
  }
  if (__DEV__ && effect.onTrigger) {
    effect.onTrigger({
      effect,
      target: effect.computed!.target,
      type: TriggerOpTypes.SET,
      key: effect.computed!.key
    })
  }
  effect.dirty = true
  if (effect.scheduler) {
    effect.scheduler()
  }
}
reactive.ts(响应式对象核心)

路径:packages/reactivity/src/reactive.ts

typescript 复制代码
import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from './baseHandlers'
import { mutableCollectionHandlers, readonlyCollectionHandlers, shallowCollectionHandlers, shallowReadonlyCollectionHandlers } from './collectionHandlers'
import { UnwrapRef, Ref } from './ref'
import { isObject, toRawType, def } from '@vue/shared'
import { ReactiveEffect } from './effect'
import { createDep } from './dep'

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.IS_SHALLOW]?: boolean
  [ReactiveFlags.RAW]?: any
}

const reactiveMap = new WeakMap<Target, any>()
const shallowReactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>()
const shallowReadonlyMap = new WeakMap<Target, any>()

const enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2
}

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

/**
 * Creates a reactive copy of the original object.
 *
 * The reactive conversion is "deep"---it affects all nested properties. In the
 * ES2015 Proxy based implementation, the returned proxy is **not** equal to the
 * original object. It is recommended to work exclusively with the reactive
 * proxy and avoid relying on the original object.
 *
 * A reactive object also automatically unwraps refs contained in it, so you
 * don't need to use `.value` when accessing and mutating their value:
 *
 * ```js
 * const count = ref(0)
 * const obj = reactive({ count })
 *
 * obj.count++
 * obj.count // 1
 * count.value // 1
 * ```
 */
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

export declare const ShallowReactiveMarker: unique symbol

export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }

/**
 * Return a shallowly reactive copy of the original object, where only the root
 * level properties are reactive. It also does not auto-unwrap refs (even at the
 * root level).
 */
export function shallowReactive<T extends object>(
  target: T
): ShallowReactive<T> {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  )
}

type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
export type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
  ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends ReadonlyMap<infer K, infer V>
  ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends WeakMap<infer K, infer V>
  ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends Set<infer V>
  ? ReadonlySet<DeepReadonly<V>>
  : T extends ReadonlySet<infer V>
  ? ReadonlySet<DeepReadonly<V>>
  : T extends WeakSet<infer V>
  ? WeakSet<DeepReadonly<V>>
  : T extends Promise<infer V>
  ? Promise<DeepReadonly<V>>
  : T extends Ref<infer V>
  ? Readonly<Ref<DeepReadonly<V>>>
  : T extends {}
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : Readonly<T>

/**
 * Creates a readonly copy of the original object. Note the returned copy is not
 * made reactive, but `readonly` can be called on an already reactive object.
 */
export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}

/**
 * Returns a reactive-copy of the original object, where only the root level
 * properties are readonly, and does NOT unwrap refs nor recursively convert
 * returned properties.
 * This is used for creating the props proxy object for stateful components.
 */
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  )
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

export function isShallow(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}

export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

export function markRaw<T extends object>(value: T): T {
  def(value, ReactiveFlags.SKIP, true)
  return value
}

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value as object) : value

export const toReadonly = <T extends unknown>(value: T): T =>
  isObject(value) ? readonly(value as object) : value

type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
baseHandlers.ts(普通对象/数组的Proxy处理器)

路径:packages/reactivity/src/baseHandlers.ts(核心片段)

typescript 复制代码
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
import {
  ReactiveFlags,
  toRaw,
  toReactive,
  toReadonly,
  isProxy,
  isReactive,
  isReadonly,
  isShallow
} from './reactive'
import {
  isObject,
  hasOwn,
  isSymbol,
  hasChanged,
  isArray,
  isIntegerKey,
  extend,
  makeMap
} from '@vue/shared'
import { TriggerOpTypes, TrackOpTypes } from './operations'
import { Ref } from './ref'
import { warn } from './warning'

const isNonTrackableKeys = /*#__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)

const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key => (Symbol as any)[key])
    .filter(isSymbol)
)

const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)

const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as unknown[]
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values
        return arr[key](...args.map(toRaw))
      } else {
        return res
      }
    }
  })
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      pauseTracking()
      const res = (toRaw(this) as unknown[])[key].apply(this, args)
      resetTracking()
      return res
    }
  })
  return instrumentations
}

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: object, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    if (
      isSymbol(key)
        ? builtInSymbols.has(key)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }

    if (shallow) {
      track(target, TrackOpTypes.GET, key)
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - does not apply for readonly props since they are not
      // supposed to be mutated
      const shouldUnwrap = !isReadonly || !isRef(target)
      return shouldUnwrap ? res.value : res
    }

    track(target, TrackOpTypes.GET, key)

    // 如果是对象,递归创建响应式(懒监听)
    if (isObject(res)) {
      return isReadonly ? toReadonly(res) : toReactive(res)
    }

    return res
  }
}

const set = /*#__PURE__*/ createSetter()
const shallowSet = /*#__PURE__*/ createSetter(true)

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

function ownKeys(target: object): (string | symbol)[] {
  track(
    target,
    TrackOpTypes.ITERATE,
    isArray(target) ? 'length' : ITERATE_KEY
  )
  return Reflect.ownKeys(target)
}

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  {},
  mutableHandlers,
  {
    get: shallowGet,
    set: shallowSet
  }
)

export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
  {},
  readonlyHandlers,
  {
    get: shallowReadonlyGet
  }
)

2.2 核心原理 2:虚拟 DOM & Diff 算法(高效更新视图)

解决的问题:直接操作真实 DOM 是前端性能瓶颈(DOM 操作慢、重排重绘代价高),如何最小化 DOM 操作?

核心逻辑
  1. 虚拟 DOM(VNode) :本质是一个 JS 对象,包含 tag(标签名)、props(属性)、children(子节点)等字段,比如:

    js 复制代码
    // 描述 <div class="box">Hello Vue</div>
    const vnode = {
      tag: 'div',
      props: { class: 'box' },
      children: 'Hello Vue'
    };
  2. Diff 算法 :对比新旧 VNode 的核心规则(保证高效):

    • 同层比较:只对比同一层级的节点,不跨层级(比如不拿父节点和子节点比);
    • 按 key 匹配:列表渲染时用 key 标识节点,相同 key 的节点可复用,避免全量重建;
    • 最小量更新:只更新差异(比如属性变了更属性,文本变了更文本,节点新增/删除才操作 DOM)。
      Vue2 和 Vue3 的 Diff 算法核心目标一致(同层比较、最小化 DOM 操作) ,但 Vue3 针对 Vue2 的性能短板做了底层逻辑重构,尤其是列表对比场景的优化,差异非常显著。下面从「核心逻辑」「关键优化」「性能差异」三个维度拆解,附简化源码对比,让你清晰理解区别。
Vue2 vs Vue3 Diff 算法核心差异表
维度 Vue2 Vue3
列表对比策略 单指针遍历 + 索引对比 双端比较 + 最长递增子序列
移动节点判定 按索引暴力对比,无法判断「无需移动的节点」 最长递增子序列精准判定,仅移动最少节点
性能(列表增删) 差(头部/中间插入触发大量 DOM 操作) 优(仅操作必要节点)
适用场景 简单列表(无频繁增删) 复杂列表(频繁增删、排序)
核心源码位置 src/core/vdom/patch.js packages/runtime-core/src/patchChildren.ts
简化源码
Vue2 列表 Diff 核心
javascript 复制代码
// Vue2 列表 Diff 核心:单指针遍历,按索引对比
function updateChildren(parentElm, oldCh, newCh) {
  let oldStartIdx = 0;
  let oldEndIdx = oldCh.length - 1;
  let oldStartVnode = oldCh[0];
  let oldEndVnode = oldCh[oldEndIdx];

  let newStartIdx = 0;
  let newEndIdx = newCh.length - 1;
  let newStartVnode = newCh[0];
  let newEndVnode = newCh[newEndIdx];

  // Vue2 仅做了简单的头尾对比(未深入优化),核心还是单指针遍历
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (oldStartVnode == null) {
      oldStartVnode = oldCh[++oldStartIdx];
    } else if (oldEndVnode == null) {
      oldEndVnode = oldCh[--oldEndIdx];
    } else if (newStartVnode == null) {
      newStartVnode = newCh[++newStartIdx];
    } else if (newEndVnode == null) {
      newEndVnode = newCh[--newEndIdx];
    } 
    // 1. 头头匹配:复用旧节点
    else if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode);
      oldStartVnode = oldCh[++oldStartIdx];
      newStartVnode = newCh[++newStartIdx];
    } 
    // 2. 尾尾匹配:复用旧节点
    else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode);
      oldEndVnode = oldCh[--oldEndIdx];
      newEndVnode = newCh[--newEndIdx];
    } 
    // 3. 头尾匹配:复用旧节点并移动DOM
    else if (sameVnode(oldStartVnode, newEndVnode)) {
      patchVnode(oldStartVnode, newEndVnode);
      parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
      oldStartVnode = oldCh[++oldStartIdx];
      newEndVnode = newCh[--newEndIdx];
    } 
    // 4. 尾头匹配:复用旧节点并移动DOM
    else if (sameVnode(oldEndVnode, newStartVnode)) {
      patchVnode(oldEndVnode, newStartVnode);
      parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
      oldEndVnode = oldCh[--oldEndIdx];
      newStartVnode = newCh[++newStartIdx];
    } 
    // 5. 无匹配:暴力查找(遍历旧列表找相同key),找不到则创建新节点
    else {
      const keyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
      const idxInOld = keyToIdx[newStartVnode.key];
      if (!idxInOld) {
        // 新节点:创建并插入
        parentElm.insertBefore(createElm(newStartVnode), oldStartVnode.elm);
      } else {
        // 匹配到key:复用节点并移动
        const vnodeToMove = oldCh[idxInOld];
        patchVnode(vnodeToMove, newStartVnode);
        oldCh[idxInOld] = null;
        parentElm.insertBefore(vnodeToMove.elm, oldStartVnode.elm);
      }
      newStartVnode = newCh[++newStartIdx];
    }
  }

  // 处理剩余节点:新增/删除
  if (oldStartIdx > oldEndIdx) {
    // 新列表有剩余 → 新增
    const before = newCh[newEndIdx + 1] ? newCh[newEndIdx + 1].elm : null;
    addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx);
  } else if (newStartIdx > newEndIdx) {
    // 旧列表有剩余 → 删除
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
  }
}

// 判断是否为相同节点(key + tag 匹配)
function sameVnode(a, b) {
  return a.key === b.key && a.tag === b.tag;
}
Vue3 列表 Diff 核心
javascript 复制代码
// Vue3 列表 Diff 核心:双端比较 + 最长递增子序列
function patchChildren(n1, n2, container) {
  const c1 = n1.children; // 旧子节点
  const c2 = n2.children; // 新子节点

  // 1. 快速路径:新列表为空 → 删除所有旧节点
  if (c2.length === 0) {
    if (c1.length > 0) unmountChildren(c1);
    return;
  }

  // 2. 快速路径:旧列表为空 → 挂载所有新节点
  if (c1.length === 0) {
    mountChildren(c2, container);
    return;
  }

  // 3. 核心:列表 Diff(双端比较 + 最长递增子序列)
  const l2 = c2.length;
  let i = 0; // 双指针起始索引
  let e1 = c1.length - 1; // 旧列表尾索引
  let e2 = l2 - 1; // 新列表尾索引

  // 步骤1:头头对比 → 复用节点
  while (i <= e1 && i <= e2 && isSameVNodeType(c1[i], c2[i])) {
    patch(c1[i], c2[i], container);
    i++;
  }

  // 步骤2:尾尾对比 → 复用节点
  while (i <= e1 && i <= e2 && isSameVNodeType(c1[e1], c2[e2])) {
    patch(c1[e1], c2[e2], container);
    e1--;
    e2--;
  }

  // 步骤3:旧列表遍历完 → 新增剩余新节点
  if (i > e1) {
    if (i <= e2) {
      const nextPos = e2 + 1;
      const anchor = nextPos < l2 ? c2[nextPos].el : null;
      while (i <= e2) {
        patch(null, c2[i], container, anchor);
        i++;
      }
    }
  }
  // 步骤4:新列表遍历完 → 删除剩余旧节点
  else if (i > e2) {
    while (i <= e1) {
      unmount(c1[i]);
      i++;
    }
  }
  // 步骤5:核心优化 → 乱序列表(双指针匹配失败),用最长递增子序列优化
  else {
    // 5.1 构建key→索引的映射表,快速查找旧节点
    const keyToNewIndexMap = new Map();
    for (let j = i; j <= e2; j++) {
      keyToNewIndexMap.set(c2[j].key, j);
    }

    // 5.2 遍历旧列表,匹配新节点并标记需要移动/删除的节点
    const toBePatched = e2 - i + 1;
    const newIndexToOldIndexMap = new Array(toBePatched).fill(0);
    let moved = false;
    let maxNewIndexSoFar = 0;

    for (let j = i; j <= e1; j++) {
      const oldVNode = c1[j];
      const newIndex = keyToNewIndexMap.get(oldVNode.key);
      if (newIndex === undefined) {
        unmount(oldVNode); // 旧节点无匹配 → 删除
      } else {
        newIndexToOldIndexMap[newIndex - i] = j + 1; // 标记旧索引(+1避免0)
        if (newIndex > maxNewIndexSoFar) {
          maxNewIndexSoFar = newIndex;
        } else {
          moved = true; // 索引递减 → 节点需要移动
        }
        patch(oldVNode, c2[newIndex], container); // 复用节点并更新属性
      }
    }

    // 5.3 最长递增子序列:计算无需移动的节点
    const increasingNewIndexSequence = moved
      ? getSequence(newIndexToOldIndexMap) // 核心:最长递增子序列
      : [];
    let seqIdx = increasingNewIndexSequence.length - 1;

    // 5.4 按最优顺序插入节点(仅移动需要移动的节点)
    for (let j = toBePatched - 1; j >= 0; j--) {
      const nextIndex = i + j;
      const nextVNode = c2[nextIndex];
      const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
      
      // 新节点:挂载
      if (newIndexToOldIndexMap[j] === 0) {
        patch(null, nextVNode, container, anchor);
      } 
      // 需要移动的节点:插入到正确位置
      else if (moved) {
        if (seqIdx < 0 || j !== increasingNewIndexSequence[seqIdx]) {
          insert(nextVNode.el, container, anchor);
        } else {
          seqIdx--; // 最长递增子序列内的节点 → 无需移动
        }
      }
    }
  }
}

// 辅助函数:判断是否为相同节点(key + type)
function isSameVNodeType(n1, n2) {
  return n1.key === n2.key && n1.type === n2.type;
}

// 核心算法:最长递增子序列(返回索引数组)
function getSequence(arr) {
  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;
}
核心流程(一句话)

数据变化 → 生成新虚拟 DOM → 对比新旧虚拟 DOM 找差异 → 只把差异更新到真实 DOM。

2.3 核心原理 3:模板编译原理(连接模板和渲染)

解决的问题:Vue 的 <template> 模板是如何变成可执行的渲染逻辑的?

解答:Vue 不直接解析模板,而是先把模板编译成「渲染函数(render)」,执行渲染函数生成虚拟 DOM,最终渲染成真实 DOM。

核心流程(三步曲)
复制代码
模板(<template>{{ name }}</template>)
  ↓ 解析(parse):把模板字符串转成 AST(抽象语法树,描述模板结构的JS对象)
  ↓ 优化(optimize):标记"静态节点"(比如纯文本 `<div>固定文字</div>`),后续更新时跳过这些节点,提升性能
  ↓ 生成(generate):把 AST 转成渲染函数(render)
Vue2 vs Vue3 编译差异
1. 解析阶段(Parse):AST 结构与新特性支持

核心目标:把模板字符串解析成描述模板结构的 AST 抽象语法树。

Vue2 解析阶段 Vue3 解析阶段
生成基础 AST 节点,区分「元素节点/文本节点/插值节点」,但粒度较粗; 重构解析器,AST 节点更精细,新增「静态节点/动态节点」的精准区分;
不支持多根节点(Fragment),模板必须有唯一根节点,否则编译报错; 原生支持 Fragment(多根节点),AST 能处理无外层包裹的模板;
仅支持基础指令(v-if/v-for/v-bind),对新语法(如 v-memo)无支持; 支持 Vue3 新特性(Teleport、Suspense、setup 语法糖、v-memo),AST 能解析这些新节点;

简化示例

  • Vue2 模板:必须有唯一根节点 <div><p>{``{ msg }}</p></div>,解析后的 AST 根节点是 div
  • Vue3 模板:支持多根节点 <p>{``{ msg }}</p><button>点击</button>,解析后的 AST 根节点是「Fragment 节点」(虚拟的根节点)。
2. 优化阶段(Optimize):静态节点处理(核心差异)

核心目标:标记 AST 中的静态节点/动态节点,减少运行时 Diff 开销。这是 Vue2 和 Vue3 编译差异最大的阶段。

Vue2 优化阶段:基础静态标记
  • 仅标记「静态根节点」(如 <div>静态文本</div>),目的是在运行时 Diff 时跳过这些节点的对比;
  • 静态节点的 VNode 仍会在每次渲染时重新创建,只是对比时跳过,无法避免创建开销;
  • 动态节点无细分标记,运行时需全量对比节点的所有属性/文本。
Vue3 优化阶段:静态提升 + 动态标记(核心优化)

Vue3 做了两大关键优化,彻底减少运行时开销:

(1)静态提升(Static Hoisting)

完全静态的节点/属性 提升到 render 函数外部,避免每次渲染都重新创建 VNode。

  • 示例模板:<div class="static">静态文本</div><p>{``{ msg }}</p>

  • Vue2 生成的 render 函数(每次渲染都创建静态节点):

    js 复制代码
    function render() {
      return _c('div', { class: 'static' }, [_v('静态文本')]), _c('p', [_v(_s(msg))])
    }
  • Vue3 生成的 render 函数(静态节点提升到外部):

    js 复制代码
    // 静态节点提升到外部,只创建一次
    const _hoisted_1 = _createVNode('div', { class: 'static' }, '静态文本')
    _hoisted_1.key = null // 标记为静态节点
    
    function render() {
      return (_hoisted_1, _createVNode('p', null, _toDisplayString(msg)))
    }
(2)动态节点精准标记

对动态节点,标记其「动态类型」(如仅文本动态、仅 class 动态、仅 style 动态),运行时只更新这些动态部分,而非全量对比节点。

3. 生成阶段(Generate):render 函数产物差异

核心目标 :把优化后的 AST 转换成可执行的 render 函数代码。

Vue2 生成阶段 Vue3 生成阶段
render 函数依赖 this(指向 Vue 实例),如 _c('div', {}, this.msg) render 函数不依赖 this,基于 setup 上下文,适配组合式 API;
生成的 VNode 无额外标记,运行时 Diff 需全量对比节点的所有属性/文本; 生成的 VNode 携带「PatchFlags(补丁标记)」,标记动态类型;
不支持 Fragment,render 函数只能返回单个 VNode; 支持 Fragment,render 函数可返回 VNode 数组;
事件处理函数每次渲染都重新创建(如 @click="() => {}"),易触发不必要的更新; 缓存事件处理函数,避免重复创建,减少更新开销;
关键优化:PatchFlags(补丁标记)

Vue3 给动态 VNode 打标记,运行时只处理标记的动态部分,示例:

js 复制代码
// Vue3 生成的 VNode(带 PatchFlags)
_createVNode('p', { 
  class: msg // 动态 class
}, null, PatchFlags.CLASS) // 标记:仅 class 动态

_createVNode('span', null, _toDisplayString(msg), PatchFlags.TEXT) // 标记:仅文本动态

运行时 Diff 时,只需检查 VNode 的 PatchFlags,比如标记为 TEXT 的节点,只对比文本内容,无需对比属性、样式等,大幅减少 Diff 开销。

Vue2 vs Vue3 模板编译核心差异表
维度 Vue2 模板编译 Vue3 模板编译
静态节点处理 标记静态节点,但每次渲染仍重新创建 VNode; 静态提升:静态节点提升到 render 外,只创建一次;
动态节点处理 无细分标记,运行时全量对比节点; PatchFlags 标记动态类型,运行时只更新动态部分;
多根节点支持 不支持,必须有唯一根节点; 支持 Fragment,可返回多根节点;
render 函数上下文 依赖 this(选项式 API); 不依赖 this(组合式 API);
新特性支持 不支持 Fragment/Teleport/Suspense; 原生支持所有 Vue3 新特性;
运行时开销 较高(全量 Diff); 极低(只处理动态部分);
实战示例:编译产物对比
模板
vue 复制代码
<template>
  <div class="container">
    <h1>Vue 编译对比</h1>
    <p class="text" :style="{ color: color }">{{ msg }}</p>
  </div>
</template>
Vue2 编译后的 render 函数
js 复制代码
function render() {
  with(this) {
    return _c(
      'div',
      { staticClass: "container" },
      [
        _c('h1', [_v("Vue 编译对比")]), // 静态节点,但每次渲染重新创建
        _c(
          'p',
          { 
            staticClass: "text",
            style: _b({}, { color: color }, false) // 动态 style
          },
          [_v(_s(msg))] // 动态文本
        )
      ]
    )
  }
}
  • 静态节点(h1)每次渲染都重新创建;
  • 动态节点(p)无标记,运行时需对比所有属性、文本。
Vue3 编译后的 render 函数
js 复制代码
// 静态节点提升到外部,只创建一次
const _hoisted_1 = _createVNode('div', { class: 'container' }, null, PatchFlags.STATIC)
const _hoisted_2 = _createVNode('h1', null, 'Vue 编译对比', PatchFlags.STATIC)

function render(_ctx, _cache) {
  return _createVNode(_hoisted_1, null, [
    _hoisted_2,
    _createVNode('p', {
      class: 'text',
      style: { color: _ctx.color }
    }, _toDisplayString(_ctx.msg), 
    PatchFlags.STYLE | PatchFlags.TEXT // 标记:仅 style 和文本动态
    )
  ])
}
  • 静态节点(div、h1)提升到外部,只创建一次;
  • 动态节点(p)带 PatchFlags,运行时只对比 style 和文本,无需对比其他属性。

2.4 核心原理 4:组件化原理(代码复用的核心)

解决问题:如何实现可复用、可组合的代码模块?

核心逻辑
  1. 组件注册 :全局注册(Vue.component)或局部注册(组件内 components),Vue 会缓存组件的配置;
  2. 组件渲染
    • 解析模板时遇到组件标签(比如 <MyButton>),先找到组件配置;
    • 创建组件实例,执行组件的渲染函数生成虚拟 DOM;
    • 递归把组件的虚拟 DOM 挂载到真实 DOM 中;
  3. 组件通信 :通过 props(父传子)、emit(子传父)、provide/inject(跨层级)、Pinia/Vuex(全局)等方式实现数据交互,保证组件间的解耦。
Vue2 vs Vue3 组件化核心差异表
维度 Vue2 组件化 Vue3 组件化
组件实例创建 基于 Vue.extend 继承,强耦合全局 Vue; 独立纯对象实例,无继承,轻量解耦;
核心 API 风格 选项式 API(data/methods/watch 等),依赖 this 组合式 API(setup/ref/reactive 等),无 this
生命周期实现 选项式钩子,绑定 this 函数式钩子,setup 中注册,无 this
多根节点支持 不支持,必须有唯一根节点; 原生支持 Fragment,多根节点无需包裹;
实例化开销 高(选项合并、原型链继承); 低(纯对象实例,无复杂合并);
通信方式 依赖 this( e m i t / emit/ emit/provide/$attrs); this(setup 参数/函数式 API);
新特性支持 不支持 Fragment/Teleport/Suspense; 原生支持所有新特性;

3 总结

  1. 响应式:让数据可监听,实现"数据变 → 通知更新",是数据驱动的基础;
  2. 虚拟 DOM + Diff:通过 JS 对象描述 DOM,只更新差异部分,解决 DOM 操作性能问题;
  3. 模板编译:把开发者友好的模板转成高效的渲染函数,连接模板和虚拟 DOM;
  4. 组件化:把页面拆成可复用的组件实例,实现代码解耦和复用。

这四大原理最终共同实现了 Vue "简单易用、高效灵活" 的核心特性,也是理解 Vue 运行机制的关键。

相关推荐
技术钱2 小时前
vue3实现时间根据系统时区转换对应的时间
javascript·vue.js
PyHaVolask2 小时前
Web 技术核心术语
前端·http·web
殷忆枫2 小时前
基于STM32的ML307R连接Onenet平台
服务器·前端·javascript
Predestination王瀞潞2 小时前
6.5.3 软件->W3C HTML5、CSS3标准(W3C Recommendation):Selector网页选择器
前端·css3·html5
Java 码农2 小时前
vue cli 环境搭建
前端·javascript·vue.js
问道飞鱼2 小时前
【前端知识】使用React+Vite构建企业级项目模板
前端·react.js·前端框架·vite
Dxy12393102162 小时前
HTML常用CSS样式推荐:打造高效、美观的网页设计
前端·css·html
酉鬼女又兒2 小时前
零基础入门前端JavaScript Object 对象完全指南:从基础到进阶(可用于备赛蓝桥杯Web应用开发赛道)
开发语言·前端·javascript·职场和发展·蓝桥杯
tlwlmy2 小时前
python excel图片批量拼接导出
前端·python·excel