一、vue3新特性

1、响应式原理(重点)

        vue3重构了响应式系统。

1.1 vue2 响应式原理

        vue2组件实例化时, 会递归遍历data返回的对象,使用 Object.defineProperty 将每个属性转换为带有 getter/setter 的访问器属性;

        如果数据中存在数组类型,vue会从写7种数组原型方法(push、pop、shift、unshift、splice、sort、reverse等7个方法),同时也会递归处理数组中的对象元素(但基本类型元素不会被深度代理)。

        同时,每个属性都关联一个 Dep 依赖管理器,用于收集依赖和派发更新。

data() {
  return {
    count: 0,
    obj: { id: 123, name: 'zhangsan' },
    arr: [2, 8, 5]
  }
}

1.1.1 核心基础:Object.defineProperty ——》对象

        vue2响应式核心思路:通过 ES5提供的API Object.defineProperty 对数据对象的属性进行“拦截”,获取属性时,进入getter方法,修改属性时,进入setter方法,从而实现数据驱动视图更新。

const obj = {};

// 定义obj的name属性,自定义getter和setter;

Object.defineProperty(obj, 'name', {
  enumerable: true, // 可枚举(for...in遍历可见)
  configurable: true, // 可配置(可后续修改或删除)
  get() {
    console.log('获取name属性');
    return 'zhangsan';
  },
  set(newVal) {
    console.log('修改name属性为:', newVal);
  }
});

obj.name; // 触发getter,输出"获取name属性"
obj.name = 'lisi'; // 触发setter,输出"修改name属性为:lisi"
 1.1.1.1 对于数组的处理——》重写数组原型方法

采用了“重写数组原型方法”:

1、提取数组的原型对象(Array.prototype),创建一个新的原型对象(arrayMethods),并让新原型继承原有原型对象;

2、重写新原型对象上的7个可以改变数组的方法:push、pop、shift、unshift、splice、sort、reverse.

3、当数组调用这些重写方法时,先执行原方法逻辑,再触发依赖更新(dep.notify)

4、对于通过重写方法添加的新元素(如 push、unshift、splice),会递归对新元素进行劫持(Observer).


// 提取数组原型
const arrayProto = Array.prototype;
// 创建新原型,继承原原型
const arrayMethods = Object.create(arrayProto);
// 要重写的7个方法
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

methodsToPatch.forEach(method => {
  // 保存原方法
  const original = arrayProto[method];
  // 重写方法
  arrayMethods[method] = function(...args) {
    const result = original.apply(this, args); // 执行原方法
    const dep = new Dep(); // 数组的依赖容器(实际Vue2中数组的Dep挂载在Observer实例上)
    dep.notify(); // 触发依赖更新

    // 处理新增元素:对新添加的元素进行劫持
    let inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2); // splice的第三个参数及以后是新增元素
        break;
    }
    if (inserted) {
      new Observer(inserted); // 递归劫持新增元素
    }
    return result;
  };
});

// 为数组对象设置新原型
function observeArray(arr) {
  arr.__proto__ = arrayMethods;
  // 对数组现有元素进行劫持
  arr.forEach(item => new Observer(item));
}

为什么没有为数组的每个索引都使用Object.defineProperty进行处理?

1、性能开销巨大

        1)初始化成本大:数组可能包含成千上万元素,如果对每个索引都使用Object.defineProperty处理

         - 需要遍历整个数组,为每个索引建立getter/setter;

         - 每个索引都要产生独立的依赖收集器(Dep),会消耗大量内存;

         - 并且元素越多,初始化时间越长,可能导致页面卡顿;

        2、运行时开销:频繁读写数组索引(如 arr[i])会触发每个索引的 getter/setter,而数组往往是高频操作的数据结构,这会拖慢性能。

2、数组的动态操作会带来复杂性

        1)长度变更(增加或删除)时,也需要动态定义getter、setter,且需要监听length变化,实现复杂且容易出错;

        2)length属性无法完全代理:直接修改arr.length=0 会触发 length 的setter,但是Object.defineProperty 对 length 的监听在某些浏览器下行为不一致,且清除长度时无法直到哪些索引被删除了,无法正确通知依赖。

1.1.2  响应式完整流程:Observer、Dep、Watcher 协同工作 ——》“发布-订阅”

Observer:数据劫持

Dep:依赖收集

Watcher:依赖触发与视图更新

阶段一:初始化——建立监听网络

在Vue实例初始化阶段,核心任务是将data中的数据转换为响应式。

  1. 数据劫持Observer会遍历data中的所有属性,为每个属性创建一个独立的Dep实例,并重写其getter/setter

  2. 递归处理:如果属性值是一个对象,则会递归地为其子属性也执行同样的操作,确保整个对象树都是响应式的。

  3. 特殊处理数组:由于Object.defineProperty无法拦截数组的方法调用,Vue 会重写数组的pushpop等7个变异方法,使其在变更时能手动触发更新通知。

阶段二:渲染——收集依赖关系

当组件首次加载时,会进入依赖收集阶段。这个阶段的关键在于一个全局的桥梁——Dep.target

  1. 挂载 WatcherVue会为每个组件实例创建一个渲染WatcherWatcher内部的get方法执行前,会将自身赋值给 Dep.target

  2. 触发getter:接着,渲染Watcher会执行组件的渲染函数(updateComponent)。渲染函数在读取模板中使用的数据(如{{ message }})时,便会触发该数据的getter

  3. 完成收集:在getter内部,代码会检查Dep.target是否存在。因为此时Dep.target正是当前的渲染Watcher,所以会调用dep.depend(),将这个Watcher收集到当前属性对应的Dep实例的subs数组中。

  4. 清理现场:渲染完成后,会调用popTarget()Dep.target重置为之前的状态(通常是null),防止后续的无关操作触发依赖收集。

阶段三:派发更新——触发视图重绘

当响应式数据发生变化时,流程进入派发更新阶段

  1. 触发setter当修改了data中的某个属性时,该属性的setter被调用

  2. 通知更新:在setter内部,会执行新值的响应式处理(observe(newVal)),调用所属Dep实例的dep.notify()方法。

  3. 执行 Watcherdep.notify()会遍历其subs数组中的所有Watcher,并逐个调用它们的update()方法。

  4. 异步更新与重绘update()方法不会立即执行DOM更新,而是触发一个异步更新机制。它会将当前Watcher推入一个队列,并在下一个事件循环(nextTick)中统一执行,这保证了即使多次修改同一个数据,也只会进行一次DOM更新。最终,Watcher会重新执行渲染函数,完成视图更新。

1.1.2.1 Observer:数据劫持核心

作用:是将普通对象转化为“响应式对象”——遍历对象的所有属性,通过Object.defineProperty重写其gettersetter。对于嵌套对象,会递归进行劫持(深度观测)。


class Observer {
  constructor(data) {
    this.walk(data); // 遍历对象属性进行劫持
  }

  // 遍历对象(仅处理对象,不处理数组)
  walk(data) {
    if (typeof data !== 'object' || data === null) return;
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
    });
  }

  // 核心:定义响应式属性
  // data: 对象;  key:属性名  val:属性值
  defineReactive(obj, key, val) {
    const dep = new Dep(); // 每个属性对应一个Dep实例(依赖容器)
    this.walk(val); // 递归劫持子属性(处理嵌套对象)

    Object.defineProperty(obj, key, {
      get() {
        // 收集依赖:当属性被访问时,将当前Watcher添加到Dep
        Dep.target && dep.addSub(Dep.target);
        return val;
      },
      set(newVal) {
        if (newVal === val) return; // 新值与旧值一致,不触发更新
        val = newVal;
        this.walk(newVal); // 新值若为对象,需递归劫持
        dep.notify(); // 触发依赖更新:通知所有关联的Watcher执行更新
      }
    });
  }
}
1.1.2.2 Dep:依赖收集器

每个响应式属性都有一个专属的 Dep 实例。它内部维护一个subs数组,用于存储所有依赖于该属性的 Watcher,是连接数据和视图的枢纽。

class Dep {
  constructor() {
    this.subs = []; // 存储依赖的Watcher数组
  }

  // 添加依赖(Watcher)
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub);
    }
  }

  // 触发所有依赖更新
  notify() {
    this.subs.forEach(sub => sub.update()); // 调用每个Watcher的update方法
  }
}

Dep.target = null; // 静态属性:存储当前正在执行的Watcher(全局唯一)

1.1.2.3 Watcher:依赖触发与视图更新

代表一个依赖项,是连接数据和视图的桥梁。当 Dep 通知它数据变化时,Watcher 会执行相应的回调函数(如重新渲染组件)


class Watcher {
  constructor(vm, expOrFn, callback) {
    this.vm = vm; // Vue实例
    this.expOrFn = expOrFn; // 要观测的表达式(如'name')或更新函数
    this.callback = callback; // 数据变化后的回调(如更新视图)
    this.value = this.get(); // 初始化:触发getter收集依赖
  }

  // 初始化:获取表达式的值,触发属性getter
  get() {
    Dep.target = this; // 将当前Watcher设为全局目标
    const value = this.vm[this.expOrFn]; // 访问属性,触发getter,收集依赖
    Dep.target = null; // 重置全局目标
    return value;
  }

  // 数据变化时触发:执行更新逻辑
  update() {
    const newValue = this.vm[this.expOrFn];
    const oldValue = this.value;
    if (newValue !== oldValue) {
      this.value = newValue;
      this.callback(newValue, oldValue); // 执行回调(更新视图)
    }
  }
}
1.1.3  vue2 响应式的缺陷

vue2响应式(Object.defineProperty)的局限性:只能劫持对象的已有属性;

存在的问题:

        1)无法监听对象属性的动态添加和删除;必须通过Vue.set 和 Vue.delete;

        2) 直接通过索引修改数组元素或修改length属性无法触发响应;需要通过方法Vue.set或push、pop、splice等;

        3)深度观测性能开销大:初始化时会递归劫持所有嵌套属性,若数据层级过深(如树形结构),会导致初始化速度变慢。

1.2 vue3 响应式原理(基于Proxy的代理实现)          

     目的:解决 vue2 响应式缺陷

      vue3响应式核心思路:通过Proxy创建数据对象的代理对象,拦截对象的所有操作(访问、修改、新增、删除等),在拦截器中完成依赖收集和依赖触发,同时结合 Reflect 规范化拦截操作的返回值,保证与原生行为一致。

1.2.1 核心基础:Proxy + Reflect
1.2.1.1 Proxy

目的:用于创建一个代理对象,接收两个参数:target(被代理的目标对象)和  handler(拦截器对象,定义拦截行为)。

优点:

        1)拦截范围广:可以拦截读写的所有操作(get、set、deleteProperty、has、ownKeys等13中操作);

        2)原生支持数组:可以直接监听数组的索引变化、长度变化;

        3)非侵入式:无需修改目标对象的属性(不重写getter/setter),通过代理对象间接操作目标对象。


const target = { name: 'zhangsan', age: 20 };
// 创建代理对象
const proxy = new Proxy(target, {
  // 拦截属性获取
  get(target, key, receiver) {
    console.log(`获取${key}属性`);
    return Reflect.get(target, key, receiver); // 用Reflect执行原生获取操作
  },
  // 拦截属性修改/新增
  set(target, key, value, receiver) {
    console.log(`修改/新增${key}属性为:`, value);
    return Reflect.set(target, key, value, receiver); // 用Reflect执行原生设置操作
  },
  // 拦截属性删除
  deleteProperty(target, key) {
    console.log(`删除${key}属性`);
    return Reflect.deleteProperty(target, key); // 用Reflect执行原生删除操作
  }
});

proxy.name; // 触发get,输出"获取name属性"
proxy.age = 21; // 触发set,输出"修改/新增age属性为:21"
proxy.gender = 'male'; // 触发set,输出"修改/新增gender属性为:male"
delete proxy.age; // 触发deleteProperty,输出"删除age属性"
1.2.1.2 Reflect

Reflect 保证了 Proxy 拦截时,this 正确指向代理对象,并且完整遵循 JavaScript 对象操作的默认行为,配合Proxy的“代理”操作,从而实现正确、可靠的响应式系统。

1、保证 this 指向代理对象

当使用Proxy代理一个对象,并且在对象内部的方法中访问自身属性时,如果不使用Reflect,会出现this指向错误。

const obj = {
  _value: 1,
  get value() {
    // 这里的 this 希望指向代理对象,这样才能被依赖收集
    return this._value
  }
}

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    // 错误做法:直接返回 target[key]
    // return target[key]  // ❌ 这里的 this 是原始对象 obj,不是代理对象
    
    // 正确做法:使用 Reflect
    return Reflect.get(target, key, receiver)  // ✅ this 指向 receiver(即代理对象)
  }
})
  • receiver 就是代理对象本身(或者继承自代理的对象)。

  • Reflect.get 会把第三个参数 receiver 传给内部的 getter 作为 this

  • 这样 getter 内部访问 this._value 时,实际上会再次经过 Proxy 的 get 拦截,从而被响应式系统正确追踪(依赖收集)。

2、完整模拟原始操作的返回值和行为

Proxy 的拦截方法需要返回合适的值,比如:

  • get 应该返回属性值

  • set 应该返回是否设置成功(布尔值)

  • deleteProperty 应该返回是否删除成功

Reflect 的方法与 Proxy 拦截方法一一对应,并且完全遵循 JavaScript 内部语言规范

  • 属性访问器、赋值、删除等操作的成功/失败状态

  • 原型链上的属性访问

  • 对象的不可写/不可配置等特性的正确处理

如果不使用 Reflect,手动模拟这些行为非常容易出错,而且难以处理边界情况(如只读属性、冻结对象等)。

const proxy = new Proxy(obj, {
  set(target, key, value, receiver) {
    // 错误:直接设置并返回 true
    target[key] = value
    return true  // ❌ 但没有处理可能失败的情况(如只读属性)
    
    // 正确:使用 Reflect,它会根据属性特性返回正确布尔值
    return Reflect.set(target, key, value, receiver)  // ✅
  }
})

如果对象属性是只读的,Reflect.set 会返回 false,而手动设置 target[key] = value 在非严格模式下会静默失败(返回 true 就出错了)。

1.2.2 核心模块:ReactiveEffect 与依赖收集
1.2.2.1 ReactiveEffect:副作用函数

ReactiveEffect 是依赖收集和触发的核心,对应 vue2 中的 Watcher;

作用:在副作用函数执行时收集依赖,在数据变化时重写执行副作用函数。如 依赖数据的函数、视图渲染函数、计算属性函数等。


class ReactiveEffect {
  constructor(fn, scheduler = null) {
    this.fn = fn; // 副作用函数(如视图渲染函数)
    this.scheduler = scheduler; // 可选:自定义更新调度函数
    this.active = true; // 标记副作用是否激活
    this.deps = []; // 存储当前副作用依赖的所有依赖容器(Map/Set)
  }

  // 执行副作用函数,触发依赖收集
  run() {
    if (!this.active) return this.fn(); // 非激活状态直接执行函数
    // 核心:将当前副作用设为全局目标,用于收集依赖
    effectStack.push(this);
    try {
      return this.fn(); // 执行副作用函数,会访问响应式数据,触发Proxy的get拦截
    } finally {
      effectStack.pop(); // 执行完毕后,从栈中移除
      this.cleanupDeps(); // 清理过期依赖
    }
  }

  // 清理过期依赖(避免重复收集)
  cleanupDeps() {
    for (let i = 0; i < this.deps.length; i++) {
      this.deps[i].delete(this);
    }
    this.deps.length = 0;
  }

  // 停止副作用(不再响应数据变化)
  stop() {
    if (this.active) {
      this.cleanupDeps();
      this.active = false;
    }
  }
}

// 全局栈:存储当前正在执行的副作用函数(支持嵌套)
const effectStack = [];
// 全局变量:当前正在执行的副作用函数
let activeEffect = null;

// 辅助函数:创建并执行副作用函数
function effect(fn, options = {}) {
  const effect = new ReactiveEffect(fn, options.scheduler);
  effect.run(); // 初始化执行,收集依赖
  // 返回停止函数
  return () => effect.stop();
}
1.2.2.2 依赖收集:TargetMap的结构设计

Vue3采用“三层Map/Set”结构(targetMap)存储依赖关系,相比Vue2的Dep容器,结构更清晰、更高效:

第一层:targetMap(Map):key为被代理的目标对象(target),value为depsMap(依赖Map);

第二层:depsMap(Map):key为目标对象的属性名(key),value为dep(依赖Set);

第三层:dep(Set):存储当前属性对应的所有ReactiveEffect实例(副作用函数)。
 


// 全局依赖映射表
const targetMap = new Map();

// 收集依赖:target(目标对象)、key(属性名)
function track(target, key) {
  // 若没有活跃的副作用函数,无需收集依赖
  if (!activeEffect) return;
  // 1. 获取目标对象对应的depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  // 2. 获取属性对应的dep(依赖Set)
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  // 3. 将当前副作用函数添加到dep(避免重复添加)
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    // 4. 反向记录:将dep添加到副作用函数的deps数组(用于后续清理)
    activeEffect.deps.push(dep);
  }
}

// 触发依赖:target(目标对象)、key(属性名)
function trigger(target, key) {
  // 1. 获取目标对象对应的depsMap
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  // 2. 获取属性对应的dep(依赖Set)
  const dep = depsMap.get(key);
  if (dep) {
    // 3. 遍历所有副作用函数,执行更新
    const effects = [...dep]; // 复制一份,避免执行过程中Set结构变化
    effects.forEach(effect => {
      // 若有自定义调度函数,执行调度函数;否则直接执行副作用函数
      if (effect.scheduler) {
        effect.scheduler();
      } else {
        effect.run();
      }
    });
  }
}
1.2.3  ref 、reactive、toRef

vue3提供了两种响应式API:ref 和 reactive;它们底层共享同一套依赖收集(track)和 触发更新(trigger)系统

1.2.3.1 reactive 原理

作用:将引用类型转换为响应式代理(Proxy)

限制:

        1)只能用于引用类型(对象、数组、Map、Set等),不能用于基本类型;

        2)不能解构:解构会失去响应性(访问的是原始值);

        3)不能直接赋值整个响应式对象:如 state = newState 会断开响应式连接。‘’

关键优化:reactive采用“懒代理”模式,仅在访问嵌套属性时才对其进行代理,而非初始化时递归代理所有属性,大幅提升了深层数据的初始化性能

1.2.3.2 ref原理

作用:包装任意类型,使其具有响应性;

对于基本类型,它创建一个带有 value 属性的RefImpl对象(有个私有属性__v_isRef为true),将值存放到 value属性中,并利用类的getter/setter来追踪依赖和触发更新。

对于对象或数组,内部会调用reactive 进一步转换。

特点:

1)包装基本类型:通过 .value 访问/修改,内部使用 依赖收集(track)和 触发更新(trigger)系统 实现响应性;

2)对象/数组自动 reactive:如果传入对象/数组,内部会调用reactive 将其转换为深层响应式代理,访问 ref.value 得到的是 reactive 对象;

3)模板自动解包:在模板中直接使用ref时, vue编译器会自动添加.value;

4)支持响应式替换:直接给 ref.value 赋值一个新对象,新对象会自动reactive,而reactive对象整体替换会失去响应性。

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

class RefImpl {
  private _value;
  private dep; // 依赖集合

  constructor(value) {
    this._value = value;
    this.dep = new Set();
  }

  get value() {
    // 收集依赖:将当前活跃的副作用函数(effect)加入 dep
    track(this, 'value');
    return this._value;
  }

  set value(newVal) {
    if (newVal !== this._value) {
      this._value = newVal;
      // 触发依赖:执行 dep 中所有副作用函数(重新渲染、计算属性、watch等)
      trigger(this, 'value');
    }
  }
}
1.2.3.3  toRef/toRefs:将响应式对象转换为ref对象集合

作用:解构或提取响应式对象中的属性时,仍能保持原属性的响应式连接

当需要解构响应式对象(reactive创建的对象)的属性时,直接解构会丢失响应式(因为解构后得到的是普通值)。

toRef —— 为单个属性创建响应式引用

适用场景:

  • 只关心响应式对象中的某一个属性,需要把它传递给组合式函数或作为计算属性依赖。

  • 将 props 中的某个属性转为 ref 以便在内部使用(结合 toRef 和 props

const state = reactive({ count: 0 })

// 创建一个 ref,关联到 state.count
const countRef = toRef(state, 'count')

// 双向同步
countRef.value++     // 修改 ref → state.count 变为 1
console.log(state.count) // 1

state.count++        // 修改原对象 → countRef.value 变为 2
console.log(countRef.value) // 2




// 场景2
// 在 setup 函数中处理 props
const props = defineProps(['foo'])
const fooRef = toRef(props, 'foo')
// 现在 fooRef 会随着父组件传入的 props.foo 变化而变化

toRefs —— 为对象所有属性批量创建 ref

import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0, name: 'Vue' })

const stateRefs = toRefs(state)
// 结果:{ count: ref(0), name: ref('Vue') }

// 解构后仍然保持响应式连接
const { count, name } = stateRefs

count.value++        // 同时更新 state.count
console.log(state.count) // 1

经典场景:在setup中解构reactive对象后返回给模板

export default {
  setup() {
    const state = reactive({ count: 0, name: 'Vue' })

    // toRefs 后解构,保留响应式
    return { ...toRefs(state) }   // 模板中可直接使用 count 和 name
  }
}
1.2.3.4 shallowReactive / shallowRef


适用场景:仅需要浅层响应式(只监听对象第一层属性,嵌套属性变化不触发更新),优化大型对象的性能。
底层:shallowReactive 只对目标对象的第一层属性创建 Proxy 代理,不递归嵌套属性;shallowRef 仅监听 value 本身的替换,不监听 value 内部属性的变化。


1.2.3.5 readonly / shallowReadonly


适用场景:定义只读的响应式数据(禁止修改,修改时会给出警告)。
底层:创建 Proxy 代理时,拦截 set、deleteProperty 等修改操作,直接抛出警告并阻止操作执行。

1.2.3.6 customRef 自定义响应式数据
import { customRef } from 'vue'

const count = customRef((track, trigger) => {
  let value = 0

  return {
    get() {
      track()
      return value
    },
    set(newValue) {
      value = newValue
      trigger()
    }
  }
})

2、代码组织方式  Composition API

2.1 Vue2 Options API (选项式API)

将组件的不同部分(data、methods、computed、watch)分门别类,在不同的“选项块”中定义。

优点:入门简单、结构清晰;对于小型或简单组件非常直观。

缺点:逻辑关注点分散。组件复杂时,同一个功能的逻辑可能会分散在文件的不同部分,可维护性格低;且代码复用主要依赖mixins,容易引发命名冲突和数据来源不清晰的问题。

2.2 vue3 Compositon API(组合式API)

        允许根据逻辑功能来组织代码,将一个功能相关的状态、方法、计算属性等代码都聚合在一个setup函数或一个独立的组合式函数中。

优点:高内聚、低耦合相关逻辑都被聚合在一起,更利于阅读和维护,尤其时大型组件的开发。同时,逻辑复用更加优雅和简单,只需要将逻辑封装到独立的组合式函数中,即可在组件间自由复用。——》自定义Hook(必须以“use”开头)

3、性能:更快、更轻、更高效

除了响应式系统的革新,Vue 3 在编译和运行时也做了大量优化。

3.1 编译优化 

将整体渲染速度提升了1.3至2倍。

静态提升处理了静态内容的复用;

对于动态内容,通过 PatchFlag(动态标记)和 Block Tree 实现了“靶向更新”。

动态节点标记:编译器在生成 VNode 时,会根据绑定的类型给动态节点加上一个补丁标志(PatchFlag).运行时根据标志决定该节点需要检查哪些内容。

        在 patch 过程中,如果 VNode 没有 patchFlag 说明是静态节点,直接跳过,不再深入比较其子节点;如果有 patchFlag,则根据标志只检查对应部分,不再递归比较整个子树;避免了无意义的比对,diff 复杂度大幅下降.

vue3 引入 Block 概念:编译器识别出任何可能包含动态子节点的节点,都会将其标识为 Block.

        在创建 VNode 时,Block 会收集其内部所以带有 patchFlag 的子孙节点,放入一个名为 dynamicChildren 的数组中,这个数组是扁平的,不关心层级;当 Block 节点需要更新时,直接遍历它的 dynamicChildren 数组,对每个动态子节点执行更新操作,不用遍历静态父节点.

嵌套 Block 形成 Block Tree

        每个组件实例内都有一个 Block Tree。更新时,只需要递归遍历 Block Tree 中的 Block,在每个 Block 中处理 dynamicChildren;

  1. 静态提升:将不会变化的节点提升到渲染函数外部并缓存,每次重新渲染时直接复用,避免重复创建。

  2. 补丁标志(Patch Flags):标记动态节点类型,避免全量diff,运行时只需更新带标记的节点及其特定属性,跳过静态内容。

  3. 缓存事件处理函数:编译时缓存事件处理函数(如@click),避免每次渲染创建新函数导致不必要的更新;

  4. 预字符串化:将多个连续的静态节点合并成一个HTML字符串,一次性创建并插入DOM,减少VNode数量为每个动态节点都打上标记,

  5. Block Tree:通过Block Tree将模板划分为多个稳定的块,每个块内部的动态节点会被收集到一个数组,更新时直接遍历,极大提升更新效率。

3.2   Diff 算法

3.2.1.vue2 的 diff 算法(双端比较)

vue2 采用双端比较策略,同时比较新旧 VNode 数组的头部和尾部(同一层级进行比较)

  • 头头比较:旧头 vs 新头
  • 尾尾比较:旧尾 vs 新尾
  • 头尾比较:旧头 vs 新尾
  • 尾头比较:旧尾 vs 新头
  • 查找比较:如果以上都不匹配,则用新节点的 key 去旧节点列表中查找可复用节点

优点:

  • 对于常见的列表操作(头部/尾部插入、删除、反转)效率很高
  • 不需要创建额外的映射表

缺点:

  • 对于大量无 key 或 key 使用不当的场景,会退化为O(n²)的遍历查找
  • 无法跳过静态子树,每次都要完整比较
3.2.2 Vue3 的 diff 算法(最长递增子序列+编译时优化)

vue3 在双端比较的基础上,引入了最长递增子序列算法来优化移动节点的操作,同时结合编译时标记(PatchFlag)跳过静态部分。

核心步骤(处理相同类型子节点数组时):

  1. 前置后置预处理:从左到右对比相同节点,跳过头部相同的静态节点;再从右到左对比尾部相同的节点
  2. 生成新节点位置映射:构建剩余新节点(未匹配到的)的 key → 索引 映射表
  3. 遍历旧节点:尝试用映射表匹配可复用的旧节点,记录其在旧数组中的位置
  4. 计算最长递增子序列:LIS 代表那些不需要移动的节点,其他节点需要移动或插入
  5. 移动/挂载:从后往前遍历新节点,根据 LIS 决定是移动还是挂载新节点

时间复杂度:O(n log n) (由于 LIS 中使用二分查找)

    3.3 Tree-shaking 支持 ——》“更小的体积”

    按需引入,没被使用的功能,不会被打包到生产环境,减少打包提交。

    3.4 重构响应式系统   

    Object.defineProperty ——》 Proxy

    4、TypeScript支持:从“兼容”到“原生”

    Vue 3 对 TypeScript 的支持是质的飞跃

    • Vue 2:由于 Options API 的 this 上下文机制,TypeScript 的类型推断非常困难,需要依赖 vue-class-component 等第三方库才能获得较为完整的类型支持。

    • Vue 3:整个框架由 TypeScript 重写,提供了开箱即用的完美类型支持。结合 defineComponent 和 Composition API,TypeScript 能精准地推断出 propsdatamethods 等所有内容,开发体验极佳。

    5、新功能

    1. 自定义组合函数 —— 代码复用
      // useLocalStorage.js
      import { ref, watchEffect } from 'vue'
      export function useLocalStorage(key, defaultValue) {
          // 从localStorage读取初始值
          const value = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
      
          // 监听变化并同步到localStorage
          watchEffect(() => {
               localStorage.setItem(key, JSON.stringify(value.value))
          })
          return value
      }

    2. Fragment:vue2.x中每个模板都需要一个根节点,而vue3中可以有多个根节点,可以节省到不必要的节点元素;
      <template>
        <div>Hello</div>
        <div>World</div>
      </template>
    3. Teleport:允许你将子组件渲染到DOM的任何位置,而不仅仅是它的父组件中。
      <template>
        <div>
          <!-- 这里的按钮逻辑上属于该组件,但弹窗内容可以传送到 body -->
          <button @click="open = true">打开模态框</button>
          
          <Teleport to="body">
            <div v-if="open" class="modal">
              <p>这是被传送到 body 下的模态框</p>
              <button @click="open = false">关闭</button>
            </div>
          </Teleport>
        </div>
      </template>
      
      <script setup>
      import { ref } from 'vue'
      const open = ref(false)
      </script>
      主要适用场景:
      1. 模态框:避免父容器z-index、transform、overflow:hidden 等样式影响遮罩层;
      2. 复杂布局中的悬浮元素:如工具提示、日期选择器面板等;
      3. 下拉菜单/弹出层:防止被父容器裁剪或限制滚动位置;
      4. 全局通知/Toast:让提示信息脱离组件层级,始终显示到页面顶层。
    4. Suspense:允许我们在程序等待异步组件渲染时渲染一个加载内容,给用户一个友好的体验;异步组件需要通过 defineAsyncComponent 来显示的定义。
      <Suspense>
        <template #default>
          <AsyncComponent />
        </template>
        <template #fallback>
          <div>Loading...</div>
        </template>
      </Suspense>
    5. 全局API调整 createApp
      import { createApp } from 'vue';
      const app = createApp(App);
      app.use(router).mount('#app');
      

      使用createApp() 代替 new Vue(),避免全局配置污染

    6. 支持多个 v-model 绑定
      <ChildComponent v-model:name="name" v-model:age="age" />
      
    7. 生命周期调整

      setup() 替代 beforeCreate 和 created:在 Vue 3 中,这些初始化逻辑都应该在 setup() 函数内编写,它会在组件实例创建之前执行。钩子名称变更:钩子名称统一加了 on 前缀。

    6、生态工具

    Vue Router 4:支持 Vue 3 的路由库。库适配。
    Vite:新一代构建工具,提供极速开发体验。
    Pinia:官方推荐状态管理工具

    更多推荐