Object.defineProperty和Proxy的比较
一.Object.defineProperty 介绍
1.基本用法
Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(obj, prop, desc);
//obj 需要定义属性的当前对象
//prop 当前需要定义的属性名
//desc 属性描述符
通过Object.defineProperty()为对象定义属性,有两种形式,且不能混合使用,分别为数据描述符,存取描述符,下面分别描述下两者的区别:
1.数据描述符 --特有的两个属性(value,writable):
const Obj = {}
Object.defineProperty(Obj, 'age', {
val: 18,
writable: false ,// 是否可以改变,默认false不可以改变
enumerable:true ,//是否可枚举
configrable:true//是否可配置
});
Obj.age=20;
console.log(Obj.age)//undefined,因为writable不能改变
2.存取描述符 (是由 getter、setter 函数功能来描述的属性)
Object.defineProperty(data, key, {
enumerable: true,
get() {
console.log('劫持了');
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
}
});
2.在vue2.x中的应用
有很多人说Object.defineProperty不能监听数组的变化,这种说法是不严谨的,准确的说是不能监听到数组某些方法的变化。
function Observer(data) {
for (let key in data) {
let val = data[key];
Object.defineProperty(data, key, {
enumerable: true,
get() {
console.log('数组劫持了');
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
}
});
}
}
let arr=['张','王','李','赵']
Observer(arr);
arr.pop()//会触发get函数,控制台会打印「数组劫持了」
arr.push('周');//不会触发,劫持失败
总结:数组的[‘push’, ‘unshift’, ‘splice’, ‘reverse’, ‘sort’, ‘shift’, ‘pop’]这些方法中splice、‘push’、unshift是劫持不到的,在vue2.0源码中,是对数组的这三个方法进行了重写。具体如下:
let arrayProto = Array.prototype;
let newProto = Object.create(arrayProto);
const arrMethods=['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'];
arrMethods.forEach(method => {
newProto[method] = function(...args) {
let inserted = null;
switch (method){
// args 是一个数组
case 'push':
inserted = args;
break;
case 'unshift':
inserted = args;
break;
case 'splice': //splice方法有三个参数,第三个参数才是被新增的元素
inserted = args.slice(2);//slice返回一个新的数组
break;
}
if (inserted) ArrayObserver(inserted); //因为inserted是一个新的数组项,所以要对数组的新增项重新进行劫持
arrayProto[method].call(this, ...args); //调用数组本身对应的方法
}
})
function observer(obj){
...
//通过Object.defineProperty进行循环递归绑定
}
function ArrayObserver(obj){ //对数组的新增项进行监控
obj.forEach(item => {
observer(item);
});
}
二.Proxy 介绍
基本用法
语法:
let p = new Proxy(target, handler);
1.target :需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
2.handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器)。具体的handler相关函数请查阅官网
let arr=['张','王','李','赵']
let testArr = new Proxy(arr, {
get(target, key) {
console.log('获取了getter属性');
return target[key];
}
});
testArr.push('周') //获取了getter属性
三.Object.defineProperty和Proxy对比
- Proxy性能优于Object.defineProperty。 Proxy代理的是整个对象,Object.defineProperty只代理对象上的某个属性,如果是多层嵌套的数据需要循环递归绑定;
- 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到,需要借助$set方法;
- 数组的某些方法(push、unshift和splice)Object.defineProperty监听不到,Proxy可以监听到;
- Proxy在ie浏览器存在兼容性问题