数据劫持即便用Object.defineProperty()实现了vue的双向绑定。先来看看它是如何实现的vue
let obj = {}, txt = '' Object.defineProperty(obj,'txt',{ set(val) { console.log('set ....') txt = val || ''; }, get() { //获取obj.txt时会触发get的回调。 console.log('get ....') return txt } })
Object.defineProperty的缺点
一、没法监听到数组的变化
举个例子数组
//当被监听的属性是数组时 let arr = [1,2,3] let obj = {} Object.defineProperty(obj,'arr',{ set(val) { console.log('set',val) arr = val }, get() { console.log('get') return arr } }) obj.arr.push(4) // get 其实是改变arr的值可是却没有执行set而是执行了get obj.arr = [1,2,3,4] // 执行了set
当被监听的属性是数组,这几个方法push、pop、shift、unshift、splice、sort、reverse不会触发set。vue将这几个修改原始的数组的方法称为变异方法app
二、必须遍历对象的每个属性测试
Object.keys(obj).forEach(key=>{ Object.defineProperty(obj,key,{ //.... }) })
三、必须深层遍历嵌套对象ui
let person = { name:{ firstName:'chan', lastName:'louis' } }
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('has change'); // 调用对应的原生方法并返回结果 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'); // 不输出内容
proxy即代理的意思。我的理解,创建一个proxy代理对象(Proxy的实例),接受你要监听的对象和监听它的handle两个参数。当你要监听的对象发生任何改变,都会被proxy代理拦截来知足需求。this
var arr = [1,2,3] var handle = { //target目标对象 key属性名 receiver实际接受的对象 get(target,key,receiver) { console.log(`get ${key}`) // Reflect至关于映射到目标对象上 return Reflect.get(target,key,receiver) }, set(target,key,value,receiver) { console.log(`set ${key}`) return Reflect.set(target,key,value,receiver) } } //arr要拦截的对象,handle定义拦截行为 var proxy = new Proxy(arr,handle) proxy.push(4) //能够翻到控制台测试一下会打印出什么
一、使用proxy能够解决defineProperty不能监听数组的问题,避免重写数组方法;
二、不须要再遍历key。
三、Proxy handle的拦截处理器除了get、set外还支持多种拦截方式,具体请查阅官方文档(https://developer.mozilla.org...)
四、嵌套查询。实际上proxy get()也是不支持嵌套查询的。解决方法:prototype
let handler = { get (target, key, receiver) { // 递归建立并返回 if (typeof target[key] === 'object' && target[key] !== null) { return new Proxy(target[key], handler) } return Reflect.get(target, key, receiver) } }