前言
相比于vue2.x的Object.defineProperty,vue3.x中的reactive中主要是利用Proxy去实现,这里对其进行模拟实现。
一. 完整代码
const reactMap = new WeakMap();
const ReactiveFlags = {
IS_REACTIVE : "isReactive"
}
// 将数据转化成响应式的数据,只能做对象的代理
function reactive(target) {
if(!(typeof target === 'object' && target !== null)) {
return;
}
if(target[ReactiveFlags.IS_REACTIVE]) {
return target
}
let exisProxy = reactMap.get(target);
if(exisProxy) {
return exisProxy
}
const proxy = new Proxy(target,{
get(target,key,receiver) {
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver) {
return Reflect.set(target,key,value,receiver)
}
});
reactMap.set(target,proxy)
return proxy
}
一. 为什么用Reflect.get和Reflect.set?
除了语义化更强些外,其实也更好地兼容了taget里面有get和set的情况
比如
const obj = {
name: 'sandy',
get formatValue() {
return `format${this.name}`
}
}
如果用target[key]的方式访问的话,get只会执行一次,即只能监控到key为formatValue的情况,但是因为formatValue依赖于name属性,所以当访问formatValue属性的同时,get应该执行2次,key为formatValue和name。而该功能Reflect.get正好可以满足。
Reflect.set类似于Reflect.get。
二. 处理相同target的问题
测试代码
const target = {name: 'sandy'}
const t1 = reactive(target);
const t2 = reactive(target);
核心代码
const reactMap = new WeakMap();
let exisProxy = reactMap.get(target);
if(exisProxy) {
return exisProxy
}
reactMap.set(target,proxy)
如果删掉上面的核心代码,则t1和t2会分别创建一个proxy,因为是同一个target,所以可以节省一次创建proxy的过程;
可以用weakMap,对target进行标识,如果已经代理过的话,则直接返回原来代理的值;
使用weakMap的好处是不会造成内存泄漏,但是要注意weakMap的key只能为对象
三. 处理target为之前代理过的proxy的情况
比如
const target = {name: 'sandy'}
const t1 = reactive(target);
const t2 = reactive(t1);
像这种情况,t2返回的就是t1,所以我们也可对此优化
核心代码
const ReactiveFlags = {
IS_REACTIVE : "isReactive"
}
if(target[ReactiveFlags.IS_REACTIVE]) {
return target
}
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
实现逻辑:
- 第一次reactive(target);正常实现一次代理逻辑;
- 第二次reactive(t1),因为此时的target为t1,之前走过一次代理了,所以当target[ReactiveFlags.IS_REACTIVE]执行时会访问get中的
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
返回结果为true之后,所以直接返回了target