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,直接操作对象即可。

相关推荐
Java 码农13 分钟前
nodejs koa留言板案例开发
前端·javascript·npm·node.js
ZhuAiQuan36 分钟前
[electron]开发环境驱动识别失败
前端·javascript·electron
nyf_unknown41 分钟前
(vue)将dify和ragflow页面嵌入到vue3项目
前端·javascript·vue.js
胡gh1 小时前
数组开会:splice说它要动刀,map说它只想看看。
javascript·后端·面试
胡gh1 小时前
浏览器:我要用缓存!服务器:你缓存过期了!怎么把数据挽留住,这是个问题。
前端·面试·node.js
你挚爱的强哥1 小时前
SCSS上传图片占位区域样式
前端·css·scss
奶球不是球1 小时前
css新特性
前端·css
Nicholas681 小时前
flutter滚动视图之Viewport、RenderViewport源码解析(六)
前端
无羡仙1 小时前
React 状态更新:如何避免为嵌套数据写一长串 ...?
前端·react.js
TimelessHaze2 小时前
🔥 一文掌握 JavaScript 数组方法(2025 全面指南):分类解析 × 业务场景 × 易错点
前端·javascript·trae