Vue2 和 Vue3 的响应式原理对比

Object.defineProperty 与 Proxy 对比

  • 前言
  • [一、Vue2 的响应式原理](#一、Vue2 的响应式原理)
  • [二、Vue3 的响应式原理](#二、Vue3 的响应式原理)
  • 三、性能优化
  • 总结

前言

响应式系统是 Vue 框架的核心机制之一,通俗易懂的来说 vue2需要手动登记,只有被用到的才会被记录vue3全自动监控

一、Vue2 的响应式原理

  1. 核心实现:Object.defineProperty
    Vue2 的响应式通过 数据劫持 实现,其核心是对对象属性的 getter 和 setter 进行拦截。
javascript 复制代码
// 定义响应式对象
function defineReactive(obj, key, val) {
  const dep = new Dep(); // 依赖收集容器(每个属性对应一个 dep)

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() { 
      dep.depend();   // 收集依赖:将 Watcher 添加到 dep 中 
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify(); // 触发更新:通知所有 Watcher 执行回调
    }
  });
}

// 递归遍历对象属性
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]); // 登记监控 key
  });
}
  1. 依赖收集与触发机制
  • Dep 类:每个属性对应一个 Dep 实例,用于存储所有依赖该属性的 Watcher。
  • Watcher 类:代表一个视图或计算属性的依赖,当数据变化时触发回调。

依赖收集流程

  • 组件渲染时触发 getter。
  • 将当前 Watcher(如渲染函数)添加到 Dep 的订阅列表中。

触发更新流程

  • 属性被修改时触发 setter。
  • 通过 dep.notify() 通知所有订阅的 Watcher 执行更新。
  1. 局限性
  • 无法检测新增/删除对象属性
    需使用 Vue.set() 或 Vue.delete() 强制触发响应。
javascript 复制代码
// 动态属性添加 API
function set(target, key, val) {
  if (Array.isArray(target)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  
  if (key in target) {
    target[key] = val
    return val
  }
  
  const ob = target.__ob__
  if (!ob) {
    target[key] = val
    return val
  }
  
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
  • 数组需要特殊处理
    Vue2 重写了数组的 push、pop 等方法,需通过原型链劫持实现响应式。
javascript 复制代码
// 数组原型劫持
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]

methodsToPatch.forEach(method => {

  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    
    // 处理新增元素
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    ob.dep.notify() // 手动触发更新
    return result
  })
})
  • 性能开销大
    初始化时递归遍历对象所有属性,对深层嵌套对象不友好。
    如果对象有 1000 个属性,需要逐个递归,耗时较长。

二、Vue3 的响应式原理

1.核心实现:Proxy

简化版代码实现

javascript 复制代码
// 响应式入口
function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key); // 依赖收集
      const res = Reflect.get(target, key, receiver);
      if (typeof res === 'object' && res !== null) {
        return reactive(res); // 递归代理嵌套对象(惰性代理)
      }
      return res;
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key); // 触发更新
      }
      return result;
    },
    deleteProperty(target, key) {
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const result = Reflect.deleteProperty(target, key);
      if (hadKey) {
        trigger(target, key); // 触发更新
      }
      return result;
    }
  };
  return new Proxy(target, handler);
}

// 依赖收集与触发(简化版)
const targetMap = new WeakMap(); // 存储所有响应式对象及其依赖

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); // 存储当前激活的 effect
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  effects && effects.forEach(effect => effect());
}
  1. ref 的实现
javascript 复制代码
class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
    this.dep = new Set()
  }

  get value() {
    trackRefValue(this) // 依赖收集
    return this._value
  }

  set value(newVal) {
    if (hasChanged(newVal, this._value)) {
      this._value = isObject(newVal) ? reactive(newVal) : newVal
      triggerRefValue(this) // 触发更新
    }
  }
}

function trackRefValue(ref) {
  if (activeEffect) {
    trackEffects(ref.dep)
  }
}

function triggerRefValue(ref) {
  triggerEffects(ref.dep)
}
  1. 核心改进
  • 动态属性监听
    支持对象属性的动态增删,无需特殊 API。
  • 原生数组响应式
    可直接通过索引修改数组或修改 length。
javascript 复制代码
	const arr = reactive([1, 2, 3]);
	arr[0] = 10; // 触发更新
	arr.length = 1; // 触发更新
  • 惰性代理
    只有被用到的属性才会被追踪。,减少初始化开销。
  • 代码更简单
    仅在实际使用的属性上触发更新,不需要处理各种特殊情况。

三、性能优化

  1. 大型对象初始化
javascript 复制代码
// 包含 1000 个属性的对象
	const bigData = { /* 1000 个属性 */ }
	
	// Vue2:立即递归转换所有属性(耗时)
	const vm = new Vue({ data: { bigData } })
	
	// Vue3:按需代理(初始化极快)
	const state = reactive(bigData)
  1. 动态属性操作
javascript 复制代码
	// Vue2 必须使用特殊API
	Vue.set(vm.data, 'newProp', value)
	
	// Vue3 直接操作
	state.newProp = value
  1. 数组性能测试
javascript 复制代码
	// 10万条数据数组
	const bigArray = new Array(1000).fill(null).map((_, i) => ({ id: i }))
	
	// Vue2 需要 200ms+ 初始化
	new Vue({ data: { bigArray } })
	
	// Vue3 需要 <10ms 初始化
	const reactiveArray = reactive(bigArray) 

总结

  • vue2手动登记,只有被用到的才会被记录,vue3全自动监控。
  • vue3性能更优:惰性代理减少初始化开销。
  • vue3代码更简洁。
相关推荐
忆琳6 分钟前
Vue3 全局自动大写转换:一个配置,全站生效
javascript·element
喵个咪9 分钟前
Headless 架构优势:内容与展示解耦,一套 API 打通全端生态
前端·后端·cms
小江的记录本13 分钟前
【JEECG Boot】 JEECG Boot——数据字典管理 系统性知识体系全解析
java·前端·spring boot·后端·spring·spring cloud·mybatis
喵个咪16 分钟前
传统 CMS 太笨重?试试 Headless 架构的 GoWind,轻量又强大
前端·后端·cms
chenjingming66617 分钟前
jmeter导入浏览器上按F12抓的数据包
前端·chrome·jmeter
张元清17 分钟前
不用 Server Components 也能做 React 流式 SSR —— 实战指南
前端·javascript·面试
前端技术19 分钟前
ArkTS第三章:声明式UI开发实战
java·前端·人工智能·python·华为·鸿蒙
码小瑞23 分钟前
画布文字在不同缩放屏幕上的归一化
前端
神の愛24 分钟前
java日志功能
java·开发语言·前端