使用Proxy实现Vue数据劫持
Proxy是什么?
Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
可以这么说Proxy是提供给开发人员一个及其接近浏览器底层的API。在没有Proxy之前,是无法改变原生JS特性的。Proxy一共提供了13种修改原生特性的能力。
let target = {
a : 1
}
let proxy = new Proxy(target, {
set(target, key, value, receiver){
console.log(key);
return Reflect.set(target, key, value, receiver);
}
})
Proxy第一个参数是目标对象,第二个参数是一个对象,其属性是当执行一个操作时定义代理的行为的函数。这时可以在第二个参数中加入一个set方法,这时可以监听到是哪个key做了改变。并且通过Reflect的set方法去模拟真实的set方法。
详细看MDN :
为什么说Proxy的性能比Object.defineProperty更好呢?
Vue3.0将放弃Object.defineProperty,改用性能更好的Proxy。
Object.definePropery是什么我就不多说了,现在网上有太多的文章去介绍它了。我们把重点放大Proxy上。
为什么说Proxy的性能比Object.defineProperty更好?
Object.defineProperty只能监听属性,而Proxy能监听整个对象,省去对非对象或数组类型的劫持,也能做到监听。
vue是对对象每一个属性进行Object.defineProperty。
Proxy:
let target = {
a : 1,
b : 2,
c : 3
}
let proxy = new Proxy(target, {
set(target, key, value, receiver){
console.log('检测到了set的key为 -> ' + key);
return Reflect.set(target, key, value, receiver);
}
})
proxy.a = '1'; // 检测到了set的key为 -> a
proxy.b = '2'; // 检测到了set的key为 -> b
proxy.c = '3'; // 检测到了set的key为 -> c
这样的话就可以监听整个对象,不用再去遍历所有属性进行劫持了,这样就省去了遍历元素。
第二点,Object.defineProperty不能监测到数组变化
先来看看尤大是怎么做的:
这里就是Array的原生方法进行重写,包括push、pop、shift、unshift、splice、sort、reverse。虽然改写了方法,但是通过下标的方式,还是无法被监测到,用Object.defineProperty是无法实现的。但是Proxy可以,具体实现如下:
let target = [1,2,3]
let proxy = new Proxy(target, {
set(target, key, value, receiver){
console.log('检测到了set的key为 -> ' + key);
return Reflect.set(target, key, value, receiver);
}
})
proxy[0] = '1'; // 检测到了set的key为 -> 0
proxy.push('2') // 检测到了set的key为 -> 3 检测到了set的key为 -> length
proxy.pop(); // 检测到了set的key为 -> length
这时就可以监听数组类型的数据了。
用Proxy实现Vue的数据劫持
完成数据劫持的主要思路就是将传入的对象,以及对象内部全部改为Proxy代理。
function isArray(o){
return Object.prototype.toString.call(o) === `[object Array]`
}
function isObject(o){
return Object.prototype.toString.call(o) === `[object Object]`
}
class Observables{
constructor(
target,
handler = {
set(target, key, value, receiver){
console.log('检测到了set的key为 -> ' + key);
return Reflect.set(target, key, value, receiver);
}
}
){
if( !isObject(target) && !isArray(target) ){
throw new TypeError('target 不是数组或对象')
}
this._target = JSON.parse( JSON.stringify(target) ); // 避免引用修改 数组不考虑
this._handler = handler;
return new Proxy(this._observables(this._target), this._handler);
}
// 为每一项为Array或者Object类型数据变为代理
_observables(target){
// 遍历对象中的每一项
for( const key in target ){
// 如果对象为Object或者Array
if( isObject(target[key]) || isArray(target[key]) ){
// 递归遍历
this._observables(target[key]);
// 转为Proxy
target[key] = new Proxy(target[key], this._handler);
}
}
// 将转换好的target返回出去
return target;
}
}
这时我们的Observables函数已经写好了,来测试一下
const o = {
a : [1, 2],
c : {
a : 1,
b : 2,
c : [
[1,2,{
d : 3
}]
]
},
b : 2
}
const ob = new Observables(o);
ob.a.push(3); // 检测到了set的key为 -> 2 检测到了set的key为 -> length
ob.c.a = 2; // 检测到了set的key为 -> a
ob.c.c[0][2].d = 6; // 检测到了set的key为 -> d
ob.b = 44; // 检测到了set的key为 -> b
可以看到push和下标都是可以监测到的。
完成,收工!
谢谢各位小伙伴的阅读