两者经常被用作数据劫持。
数据劫持:在访问或者修改对象某个属性时,通过一个代码的拦截行为,进行额外的操作或修改返回结果。数据劫持应用在数据双向绑定。
Vue2.x利用Object.defineProperty(),并将内部解耦为Observer、Dep,并使用watcher相连
Vue3.x版本后使用proxy进行
Object.defineProperty
语法
Object.defineProperty(obj,prop,descriptor)
参数解析:
obj:要在其上定义属性的对象
prop:要定义或修改的属性的名称
descriptor:将被定义或修改的属性描述符
返回值将被传递给函数的对象
劣势
- 不能监听数组的变化
- 必须遍历对象的每个属性
- 必须深层遍历嵌套的对象
不能监听数组的变化
Vue 把会修改原来数组的方法定义为变异方法 (mutation method)
非变异方法 (non-mutating method):例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。Vue 的做法是把这些方法重写来实现数组的劫持。
const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
// 这里是原生 Array 的原型方法
let original = Array.prototype[method];
// 将 push, pop 等封装好的方法定义在对象 arrayAugmentations 的属性上
// 注意:是实例属性而非原型属性
arrayAugmentations[method] = function () {
console.log('我被改变啦!');
// 调用对应的原生方法并返回结果
return original.apply(this, arguments);
};
});
let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的空数组对象
// 这样就能在调用 push, pop 这些方法时走进我们刚定义的方法,多了一句 console.log
list.__proto__ = arrayAugmentations;
list.push('d'); // 我被改变啦!
// 这个 list2 是个普通的数组,所以调用 push 不会走到我们的方法里面。
let list2 = ['a', 'b', 'c'];
list2.push('d'); // 不输出内容
必须遍历对象的每个属性
使用 Object.defineProperty() 多数要配合 Object.keys() 和遍历,,于是多了一层嵌套
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
// ...
})
})
必须深层遍历嵌套的对象
当一个对象为深层嵌套的时候,必须进行逐层遍历,直到把每个对象的每个属性都调用 Object.defineProperty() 为止。 Vue 的源码中这样的逻辑----walk 方法.
Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
语法
let p = new Proxy(target,handler);
参数:
target:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler:一个对象,其属性是当执行一个操作时定于代理的行为的函数。
- 针对对象:针对整个对象,而不是对象的某个属性
- 支持数组:不需要对数组的方法进行重载,省去了众多 hack
- 嵌套支持: get 里面递归调用 Proxy 并返回
- 其他
优化
针对对象
不需要对 keys 进行遍历。这解决Object.defineProperty() 的第二个问题.Proxy 是针对整个 obj 的。所以 obj 内部包含的所有的 key ,都可以走进 set。(省了一个 Object.keys() 的遍历)
let obj = {
name: 'Eason',
age: 30
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
proxy.name = 'Zoe' // set name Zoe
proxy.age = 18 // set age 18
支持数组
let arr = [1,2,3]
let proxy = new Proxy(arr, {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
})
proxy.push(4)
// 能够打印出很多内容
// get push (寻找 proxy.push 方法)
// get length (获取当前的 length)
// set 3 4 (设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)
嵌套支持
Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是一样的。因此也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回
let obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'cache']
}
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
// 递归创建并返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
// 以下两句都能够进入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')
其它方面
优势:Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富,Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。
劣势:Proxy 的兼容性不如 Object.defineProperty() (caniuse 的数据表明,QQ 浏览器和百度浏览器并不支持 Proxy,这对国内移动开发来说估计无法接受,但两者都支持 Object.defineProperty()),不能使用 polyfill 来处理兼容性。
补充
proxy的第二个参数 handler 对象的方法:
方法 | 用途 |
setProptotypeOf() | Object.setPropertyOf的捕捉器 |
getPrototypeOf() | Object.getPropertyOf的捕捉器 |
isExtensible() | Object.isExtensible的捕捉器 |
preventExtensions() | Object.preventExtensions方法的捕捉器 |
getOwnPropertyDescritor() | Object.getOwnPropertyDescriptor方法的捕捉器 |
defineProperty() | Object.definePropert方法的捕捉器 |
has() | in操作符的捕捉器 prop Key in proxy |
deleteProperty() | delete操作符的捕捉器 |
set | 属性设置操作的捕捉器 |
get | 属性获取操作的捕捉器 |
onwKeys | Object.getOwnPropertyNames和Object.getOwnSymbos的捕捉器 |
apply | 函数调用操作的捕捉器 |
construct | (构造函数调用)new操作符的捕捉器 |