大家好,我是前端西瓜哥,今天来看看 Object.defineProperty 和 Proxy 的区别。
我们先简单看看 Object.defineProperty 和 Proxy 的用法。
Object.defineProperty 可以在对象上修改或新增属性,并设置它的属性描述符,然后返回这个对象。
这个方法依次接受:
要改变的对象
属性名
属性描述符
const obj = {};
Object.defineProperty(obj, 'a', {
value: '前端西瓜哥'
});
console.log(obj.a);
// 前端西瓜哥
属性描述符是一个对象,有以下几个配置:
value:属性值,默认为 undefined;
configurable:属性描述符能否改变,以及属性能否被删除(通过 delete 关键字)。但 false 下,writable 可以单向变成 false,以及 value 可以改为任何值。默认为 false;
writable:值能否被修改,默认为 false;
get:getter 函数,当属性被读取时,调用该函数并使用它的返回值作为读取值。我们可以通过 this 访问当前对象。get/set 不能和 value/writable 共存,因为它们互相冲突。默认值为 undefined;
set:setter 函数,当属性被修改时,设置的新值会传给 setter 函数,我们就可以将这个新值缓存起来,默认值为 undefined;
enumerable:是否为可枚举属性,可枚举代表可以被 for…in 等 API 读取到。默认值为 false。
属性描述符还是有点复杂的,想深入学习可以去看看 MDN 文档。
Object.defineProperty 可以实现 代理,当我们通过 obj.key 的形式读取或设置值时,就可以通过 setter 和 getter 去执行一些副作用。
Vue2 正是用这个方式来实现数据的响应式,按需更新视图的。
Proxy 用于创建对象的代理。
我们通过 new Proxy 来创建代理对象,构造函数接受:
被代理的对象
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 需要遍历属性一个个加 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 更适合做代理。
我是前端西瓜哥,欢迎关注我,学习更多前端知识。