Vue3里为什么要用 Proxy 替代 defineProperty ?

494 阅读6分钟

Vue2中的响应式系统概述

简介Vue2的响应式系统

Vue2响应式系统的主要特点

1. 数据绑定:

Vue 2的响应式系统是该框架核心的一个重要部分,它使得数据和视图能够保持同步,实现了双向绑定。

2. Object.defineProperty的运用:

Vue是采用数据劫持结合发布者-订阅者模式的方式,通过使用ES5中的Object.defineProperty()方法来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。

3. 侦听器(Watcher):

为了追踪数据的变化,引入了Watcher机制。Watcher会监听数据的变化,并在变化发生时通知相关的订阅者更新视图。

defineProperty的特点和局限性

defineProperty的工作原理

var obj = {};

// 在定义属性时,使用 Object.defineProperty 进行拦截
Object.defineProperty(obj, 'name', {
  get: function() {
    console.log('访问 name 属性');
    return this._name;
  },
  set: function(value) {
    console.log('设置 name 属性为:', value);
    this._name = value;
  }
});

// 访问属性会触发 getter
console.log(obj.name);

// 设置属性会触发 setter
obj.name = 'John';

Object.defineProperty属性拦截原理:利用getset这两个trap实现拦截

defineProperty在数组变化监测方面的局限性

Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。

var arr = [1, 2, 3];
// 以下操作无法触发响应式更新
arr[0] = 100;

上述代码中,通过直接修改数组的下标 arr[0],不会触发响应式更新,因为Object.defineProperty 无法拦截这种直接对数组元素的赋值操作。

Vue3的响应式系统介绍

Vue3与Vue2的响应式系统对比:

1. Proxy取代Object.defineProperty:

Vue3中使用Proxy对象来实现响应式数据,相较于Vue2的Object.definePropertyProxy能够更全面、直接地捕获对象的操作,包括读取、写入、删除、属性枚举等,使得响应式系统更为灵活

2. 更好的数组响应式:

在Vue3中,数组的响应式处理得到显著改进。Proxy可以直接监听数组的操作,包括长度的变化和通过下标修改元素值,消除了Vue 2中处理数组变异的一些限制。

3. Refs和Reactive API:

引入了新的refreactive API,使得创建和管理响应式数据更为简单。ref用于创建基本数据类型的响应式引用,而reactive则用于创建对象或数组的响应式版本,提高了代码的可读性和可维护性。

4. Composables的引入:

Vue3将一些常用的响应式功能抽象成了可组合的函数,称为Composables。例如,computedwatch现在是独立的composables,可以更灵活地在组件中使用,提高了代码的模块化程度。

5. 动态添加响应式属性:

Vue3允许在运行时动态添加响应式属性,不再需要在对象创建时就定义好。这使得在运行时根据条件动态添加属性更为方便,增加了开发的灵活性。

Proxy的基本概念和优势

ProxyECMAScript6引入的一个新对象,用于创建一个代理对象,可以拦截并定义对象的基本操作。

Proxy是如何工作的

const p = new Proxy(target, handler)

参数

target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

代码举例

// 定义一个需要代理的对象
let individual = {
    age: 0,
    university: 'XYZU'
}


// 定义代理处理器对象
let handler = {
    get(obj, key) {
        console.log('正在获取属性值')
        // 如果对象中存在该属性,返回属性值;否则,返回默认值:99
        return key in obj ? obj[key] : 99
    },
    set(obj, key, val) {
        console.log('正在设置属性值')
        obj[key] = val
        return true
    }
}

// 使用Proxy创建代理对象
let proxiedIndividual = new Proxy(individual, handler)

// 测试get方法是否成功拦截
console.log(proxiedIndividual.university) // 输出:正在获取属性值 XYZU
console.log(proxiedIndividual.name) // 输出:正在获取属性值 99

// 测试set方法是否成功拦截
proxiedIndividual.age = 25 // 输出:正在设置属性值
console.log(proxiedIndividual.age) // 输出:正在获取属性值 25

使用Proxy有什么优势

1. 解决了Vue2中因为使用defineProperty导致的局限性

2. Vue2篡改了数组的7个API,Vue3用了Proxy就不需要了,性能更加的好

3. Proxy支持13种拦截操作

4. 数组中的优势

Proxy相对于 Object.defineProperty 在处理数组等特殊情况下具有显著的优势,主要体现在其更大的灵活性:

  1. 直接捕获数组操作: Proxy 可以直接捕获数组的各种操作,包括读取、写入、删除、枚举等,而不需要像 Object.defineProperty 一样通过对数组原型方法的重写来实现。这使得对数组的操作更为直观和精确。
  2. 动态处理数组变异方法: Proxy 能够动态捕获数组的变异方法(如pushpopshift等),而不需要事先定义。这与 Vue2 中需要对这些方法进行重写的方式相比,更加灵活和自然。
  3. 对数组长度的动态响应: 使用Proxy可以直接捕获数组长度的变化,而不需要额外的特殊处理。在Vue2中,处理数组长度变化需要通过额外的逻辑来实现,而Proxy 可以更直接地实现这种响应式。
  4. 无需递归设置: Proxy 不需要递归设置,可以自动处理嵌套数组的情况,而 Object.defineProperty 需要显式地递归设置对象属性。这减轻了初始化时的性能开销,尤其是在处理大型嵌套数组时。
  5. 更直观的语法: 使用 Proxy 可以更直观地表达响应式行为,而不需要像 Object.defineProperty 那样在对象初始化时定义每个属性的响应式规则。这使得代码更易读、易理解。

Proxy性能优势:

由于 Proxy 直接拦截操作,而不需要递归设置,因此在性能上相较于 Object.defineProperty 有一定的优势,特别是在大型项目中或者对数组进行频繁变更时。

总结

在 Vue 3 中选择 Proxy 作为响应式系统核心,主要是为了提供更灵活、高效和直观的响应式实现,满足复杂应用场景的需求。