数据劫持
数据劫持即使用 Object.defineProperty()实现了 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 的缺点
1、无法监听到数组的变化
举个例子
// 当被监听的属性是数组时
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 将这几个修改原始的数组的方法称为变异方法
2、必须遍历对象的每一个属性
Object.keys(obj).forEach(key=>{
Object.defineProperty(obj,key,{//....})
})
3、必须深层遍历嵌套对象
let person = {
name:{
firstName:'chan',
lastName:'louis'
}
}
当遇到变异方法时旧版本的 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('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 代理对象(Proxy 的实例),接受你要监听的对象和监听它的 handle 两个参数。当你要监听的对象发生任何改变,都会被 proxy 代理拦截来满足需求。
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) // 可以翻到控制台测试一下会打印出什么
1、使用 proxy 可以解决 defineProperty 不能监听数组的问题,避免重写数组方法;
2、不需要再遍历 key。
3、Proxy handle 的拦截处理器除了 get、set 外还支持多种拦截方式,具体请查阅官方文档(https://developer.mozilla.org…)
4、嵌套查询。实际上 proxy get()也是不支持嵌套查询的。解决方法:
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)
}
}