两者经常被用作数据劫持。
数据劫持:在访问或者修改对象某个属性时,通过一个代码的拦截行为,进行额外的操作或修改返回结果。数据劫持应用在数据双向绑定。
Vue2.x利用Object.defineProperty(),并将内部解耦为Observer、Dep,并使用watcher相连
Vue3.x版本后使用proxy进行
另外一种应用是在immer.js,保证数据的immutable属性,使用proxy来阻断常规的修改操作
应用:
1、Object.defineProperty()
Object.defineProperty的问题有三个:
- 不能监听数组的变化(数组的这些方法不能触发set:push、pop、shift、unshift、splice、sort、reverse)。Vue对其变异方法进行重写。
- 必须遍历对象的每个属性(Object.defineProperty多数要配合Object.keys使用)
Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { ... })})
- 必须深层遍历嵌套的对象(当一个对象为深层嵌套时,必须逐层遍历,直到把每个对象都调用到Object.defineProperty为止 )Vue源码中这样的逻辑-walk方法
2、Proxy
- 支持数组:不需要对数组方法进行重载,省去了众多hack
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.get(target, key, value, receiver) }}) proxy.push(4) //打印内容 VM1087:4 get push VM1087:4 get length VM1087:8 set 3 4 VM1087:8 set length 4 4
- 针对对象:针对整个对象,而不是对象的某个属性 (省略了Object.keys()的遍历。Reflect是一个内置对象,提供拦截js的方法。这些方法与处理器对象方法相同。Reflect不是函数对象,因此它是不可构造的。Reflect.get() Reflect.set())
- 嵌套支持:get里面递归调用proxy并返回
let obj = { a:[1, 2, 3], b:1 } 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', value) return Reflect.set(target, key, value, receiver) }} let proxy = new Proxy(obj, handler) proxy.b = 8 proxy.a.push(4)
其它方面
优势:Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富,Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。
劣势:Proxy 的兼容性不如 Object.defineProperty() (caniuse 的数据表明,QQ 浏览器和百度浏览器并不支持 Proxy,这对国内移动开发来说估计无法接受,但两者都支持 Object.defineProperty()),不能使用 polyfill 来处理兼容性