Vue2与Vue3响应式原理对比

Vue2与Vue3响应式原理对比

Vue2和Vue3能够实现响应式系统的核心API分别是:Object.defineProperty()new Proxy() 。简单来说,它们的作用都是:拦截和监听对象的变化,当数据被读取或修改时,能够"感知"到并进行后续操作(例如:更新视图)。

一、Vue2 的实现:Object.defineProperty()

Object.defineProperty() 是 ES5 中的一个方法,它可以直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回这个对象

1. 它是如何工作的?

Vue2 使用 Object.defineProperty() 来递归地遍历数据对象的所有属性,将它们转换为 gettersetter

  • getter : 当属性被读取时触发。在这里收集依赖(例如:哪个组件用到了这个数据)。
  • setter : 当属性被修改时触发。在这里通知所有依赖进行更新(例如:通知组件重新渲染)。
2. 代码示例
js 复制代码
// 一个简单的数据对象
const data = {
  count: 0
};

// 模拟 Vue2 的响应式转换
let internalValue = data.count; // 内部存储实际值

Object.defineProperty(data, 'count', {
  get() {
    console.log(`属性被读取了,当前值是: ${internalValue}`);
    return internalValue; // 读取时返回内部值
  },
  set(newValue) {
    console.log(`属性被修改了,新值是: ${newValue}`);
    internalValue = newValue;
    // 在这里,Vue 会通知所有依赖于此属性的地方进行更新
    // updateView(); // 假设这是一个更新视图的函数
  }
});

// 测试
console.log(data.count); // 输出: "属性被读取了...",然后输出 0
data.count = 5;         // 输出: "属性被修改了..."
3. 缺点与局限性
  • 对数组的限制Object.defineProperty() 无法拦截数组的索引操作 (如 arr[0] = newValue)和修改长度的操作 (如 arr.length = 0)。Vue2 通过重写数组的 7 个方法(push, pop, shift, unshift, splice, sort, reverse)来 hack 实现数组的响应式。这就是为什么在Vue2中,简单地修改修改数组会发现视图并没有改变。
  • 初始化递归遍历: Vue2 在初始化时需要递归地遍历对象的所有属性并将其转换为 getter/setter,如果对象嵌套很深,性能开销较大。
  • 无法监听对象属性的添加或删除 : 由于 Object.defineProperty() 是针对已知属性 操作的,所以无法拦截给对象新增属性obj.newProperty = value)或删除属性delete obj.property)的操作。这就是为什么 Vue2 提供了 Vue.setVue.delete 这些特殊 API 的原因。

二、Vue3 的实现:new Proxy()

Proxy 是 ES6 引入的一个全新功能。它可以创建一个对象的代理 ,从而允许我们拦截并重新定义对这个对象的基本操作。

1. 它是如何工作的?

Vue3 使用 Proxy 来"包装"整个目标对象。我们可以为这个代理对象设置一系列的"陷阱"(traps),这些陷阱可以拦截对目标对象的多种操作,而不仅仅是 getset

  • get trap : 拦截任何属性的读取操作,包括那些不存在的属性。
  • set trap : 拦截任何属性的设置操作。
  • deleteProperty trap : 拦截属性的删除操作 (delete obj.property)。
  • ...以及其他操作(如 has, ownKeys 等)。
2. 代码示例
js 复制代码
// 原始数据对象
const data = {
  count: 0
};

// 使用 Proxy 创建代理
const reactiveData = new Proxy(data, {
  get(target, property) {
    console.log(`读取了属性 ${property}, 值是: ${target[property]}`);
    return target[property];
  },
  set(target, property, value) {
    console.log(`修改了属性 ${property}, 新值为: ${value}`);
    target[property] = value;
    // 通知更新
    // triggerUpdate();
    return true; // 表示设置成功
  },
  deleteProperty(target, property) {
    console.log(`删除了属性: ${property}`);
    delete target[property];
    // 通知更新
    // triggerUpdate();
    return true; // 表示删除成功
  }
});

// 测试
console.log(reactiveData.count); // 输出: "读取了属性 count...",然后输出 0
reactiveData.count = 5;         // 输出: "修改了属性 count..."
reactiveData.newProp = "Hello"; // 输出: "修改了属性 newProp..." (Vue2 无法监听到!)
delete reactiveData.newProp;    // 输出: "删除了属性: newProp" (Vue2 无法监听到!)
3. 优势
  • 全能拦截 : 可以拦截对象上的几乎所有操作 (包括数组索引、length、属性的新增和删除),不再需要 Vue2 的那些 hack 和特殊 API。

  • 性能更好

    • 惰性递归: Proxy 只在 get 拦截到嵌套对象时才会递归代理下一层,而不是像 Vue2 那样一次性递归所有层,初始化性能更好。
    • 更好的数组支持: 天然支持数组的各种操作。
  • 更友好的 API : 因为可以拦截 delete 和新增操作,所以开发者不再需要记忆 Vue.setVue.delete,直接操作对象即可。

相关推荐
右子18 分钟前
微信小程序开发“闭坑”指南
前端·javascript·微信小程序
入秋31 分钟前
Three.js后期处理实战:噪点 景深 以及色彩调整
前端·javascript·three.js
Asort34 分钟前
JavaScript设计模式(七)——桥接模式:解耦抽象与实现的优雅之道
前端·javascript·设计模式
golang学习记36 分钟前
从0死磕全栈之Next.js 应用中的认证与授权:从零实现安全用户系统
前端
苏打水com43 分钟前
携程前端业务:在线旅游生态下的「复杂行程交互」与「高并发预订」实践
前端·状态模式·旅游
Darenm11144 分钟前
深入理解CSS BFC:块级格式化上下文
前端·css
Darenm1111 小时前
JavaScript事件流:冒泡与捕获的深度解析
开发语言·前端·javascript
渣哥1 小时前
不加 @Primary?Spring 自动装配时可能直接报错!
javascript·后端·面试
@大迁世界1 小时前
第03章: Vue 3 组合式函数深度指南
前端·javascript·vue.js·前端框架·ecmascript
小白64021 小时前
前端梳理体系从常问问题去完善-框架篇(react生态)
前端·css·html·reactjs