这是我参与8月更文挑战的第30天,活动详情查看: 8月更文挑战
使用Proxy模拟实现Vue 3.0的响应式系统。代码如下:
// 判断某个值是否是对象的辅助方法
function isObject(val){
return val !== null && typeof val === 'object'
}
// 响应式核心方法
function reactive(target){
return createReactiveObject(target)
}
// 创建响应式对象的方法
function createReactiveObject(target){
// 如果target不是对象,则直接返回
if(!isObject(target)){
return target
}
const baseHandler = {
get(target, property, receiver){
console.log('获取值')
const result = Reflect.get(target, property, receiver)
return result
},
set(target, property, value, receiver){
console.log('设置值')
const result = Reflect.set(target, property, value, receiver)
return result
},
deleteProperty(target, property){
return Reflect.deleteProperty(target, property)
}
}
}
const proxy = reactive({name:'jack'})
proxy.name = 'tom'
console.log(proxy.name)
Reflect是一个内置对象,它提供了可拦截JavaScript操作的方法。每个代理陷阱对应一个命名和参数都相同的Reflect方法。上述的代码运行结果为:
设置值
获取值
tom
同样,为了解决多层对象侦测的问题,我们需要在get陷阱函数中对返回值做一个判断,如果返回值是一个对象,则为返回值也创建代理对象,这也是一个递归调用。
修改get陷阱函数,对返回值进行判断,代码如下:
// ...
// 创建响应式对象的方法
function createReactiveObject(target){
// ...
const baseHandler = {
get(target, property, receiver){
console.log('获取值')
const result = Reflect.get(target, property, receiver)
// 对对象做递归处理
return isObject(result) ? reactive(result) : result
},
set(target, property, value, receiver){
console.log('设置值')
const result = Reflect.set(target, property, value, receiver)
return result
},
// ...
}
}
const proxy = reactive({name:'vue.js', address: {city: '北京'}})
proxy.address.city = '天津'
访问proxy.address时,会引起get陷阱调用,由于address属性本身也是一个对象,因此为该属性也创建代码。
上述代码的运行结果为:
获取值
设置值
上述代码对同一个目标对象进行了多次代理,如果每次返回一个不同的代理对象,是没有意义的,要解决这个问题,可以在为目标对象初次创建代理后,以目标对象为key,代理为value,保存到一个Map中,然后在每次创建代理前,对目标对象进行判断,如果已经存在代理对象,则直接返回代理对象,而不再创建新的代理对象。