Object.defineProperty 与 Proxy 有什么区别?

2,804 阅读4分钟

大家好,我是前端西瓜哥,今天来看看 Object.defineProperty 和 Proxy 的区别。

我们先简单看看 Object.defineProperty 和 Proxy 的用法。

Object.defineProperty

Object.defineProperty 可以在对象上修改或新增属性,并设置它的属性描述符,然后返回这个对象。

这个方法依次接受:

  1. 要改变的对象

  2. 属性名

  3. 属性描述符

const obj = {};

Object.defineProperty(obj, 'a', {
  value'前端西瓜哥'
});

console.log(obj.a);
// 前端西瓜哥

属性描述符是一个对象,有以下几个配置:

  1. value:属性值,默认为 undefined;

  2. configurable:属性描述符能否改变,以及属性能否被删除(通过 delete 关键字)。但 false 下,writable 可以单向变成 false,以及 value 可以改为任何值。默认为 false;

  3. writable:值能否被修改,默认为 false;

  4. get:getter 函数,当属性被读取时,调用该函数并使用它的返回值作为读取值。我们可以通过 this 访问当前对象。get/set 不能和 value/writable 共存,因为它们互相冲突。默认值为 undefined;

  5. set:setter 函数,当属性被修改时,设置的新值会传给 setter 函数,我们就可以将这个新值缓存起来,默认值为 undefined;

  6. enumerable:是否为可枚举属性,可枚举代表可以被 for...in 等 API 读取到。默认值为 false。

属性描述符还是有点复杂的,想深入学习可以去看看 MDN 文档。

Object.defineProperty  可以实现 代理,当我们通过 obj.key 的形式读取或设置值时,就可以通过 setter 和 getter 去执行一些副作用。

Vue2 正是用这个方式来实现数据的响应式,按需更新视图的。

Proxy

Proxy 用于创建对象的代理。

我们通过 new Proxy 来创建代理对象,构造函数接受:

  1. 被代理的对象

  2. handler 对象,其实就是一个配置对象,可以设置被代理对象的各种行为的代理,这些行为一般都是函数,称为 trap(捕捉器)。比如对象的属性被设置或修改时触发特定的函数。

看个例子:

const obj = {};

const proxyObj = new Proxy(obj, {
  get(target, prop, receiver) {
    return '前端西瓜哥' + prop;
  }
});

console.log(proxyObj.handsome);
// 前端西瓜哥handsome

当访问代理对象的属性时,就会执行该 get 函数。

其中 target 为被代理对象,prop 为被访问的属性名,recevier 为代理对象。然后 get 函数的返回值就是最后读取到的值。

这个例子中,当访问一个属性时,会返回一个加了前缀的属性名。

除了 get,Proxy 还可以代理其他的行为,比如设置属性的捕捉器 set、构造函数的捕捉器 construct、delete 操作符的捕捉器 deleteProperty 等等。

非常多,就不一个个说明了,读者可自行前往 MDN 文档学习。

Vue3 抛弃了 defineProperty,使用了 Proxy 来代理对象属性。

defineProperty 和 Proxy 的区别

defineProperty 和 Proxy 都可以对属性进行代理。

代理的粒度不同

defineProperty 只能代理属性,Proxy 代理的是对象

也就是说,如果想代理对象的所有属性,defineProperty 需要遍历属性一个个加 setter 和 getter。

而 Proxy 只需要配置一个可以获取属性名参数的函数即可。

当然,如果出现嵌套的对象,Proxy 也是要递归进行代理的,但可以做惰性代理,即用到嵌套对象时再创建对应的 Proxy。

是否破坏原对象

defineProperty 的代理行为是在破坏原对象的基础上实现的,它通常会将原本的 value 变成了 setter 和 getter。

Proxy 则不会破坏原对象,只是在原对象上覆盖了一层。当新增属性时,希望属性被代理,defineProperty 需要显式调用该 API,而 Proxy 则可以直接用 obj.key = val的形式;

代理数组属性

defineProperty 不适合监听数组属性,因为数组长度可能很大,比如几百万,一个个对索引使用 defineProperty 是无法接受的。

一种方式是重写数组的 API 方法(比如 splice),通过它们来实现代理,但它是有缺陷的:直接用 arr[1] = 100 无法触发代理。这是 Vue2 的做法。

另外,我们无法对数组的 length 做代理。这暴露了 defineProperty 的一个缺陷:设置了 configurable 为 false 的属性无法进行代理。数组的 length 就是这种情况。

图片

Proxy 则没有这个问题,它只需要设置一个 setter 和 getter,在属性变化时,能够在函数参数上拿到索引值。

代理范围

defineProperty 只能代理属性的 get 和 set。

Proxy 还能代理其他的行为,比如 delete 和 handler.getPrototypeOf()  等方法。

兼容性

Proxy 是 ES6 新增的特性,兼容性不如 defineProperty。

IE 不支持 Proxy。

且 Proxy 不能被完美 polyfill,因为它是在编程语言的层面上的修改。

Proxy 貌似还会有些性能问题,但作为标准,浏览器会持续做重点性能优化。

结尾

总的来看,Proxy 相比 defineProperty 更适合做代理。

我是前端西瓜哥,欢迎关注我,学习更多前端知识。

首发于我的公众号:前端西瓜哥