只读要达到的效果是,当用户尝试修改只读数据时,会收到一条警告信息。这样就实现了对数据的保护。这时就需要一个readonly函数,能够将一个数据变成只读的:
const obj = readonly({foo:1})
// 尝试修改数据,会收到警报
obj.foo = 2
只读本质上就是对数据对象的代理,同样可以用createReactive函数来实现。这时要为createReactive函数增加第三个参数isReadonly
// 增加第三个参数isReadonly
function createReactive(obj, isShallow = false, isReadonly = false){
return new Proxy(obj, {
set(target, key, newVal, receiver){
// 如果是只读的就打印警告信息并返回
if(isReadonly){
console.warn(`属性${key}是只读的`)
return true
}
const oldVal = target[key]
const type = Object.prototype.hasOwnProperty.call(target, key)?"EDIT":"ADD"
const res = Reflect.set(target, key, newVal, receiver)
if(target === receiver.raw){
if(oldVal !== newVal && (oldVal === oldVal || newVal === newVal)){
trigger(target, key, type)
}
}
return res
},
deleteProperty(target,key){
// 如果是只读的就打印警告信息并返回
if(isReadonly){
console.warn(`属性${key}是只读的`)
return true
}
const hasKey = Object.prototype.hasOwnProperty.call(target, key)
const res = Reflect.deleteProperty(traget, key)
if(res && hadKey){
trigger(target, key, 'DELETE')
}
return res
}
})
}
上面代理同时修改了get拦截函数和deleteProperty拦截函数,因为对一个对象来说,只读意味着既不可以设置对象的属性值,也不可以删除对象的属性。
当然如果一个数据是只读的,也就意味着没有必要为只读数据建立响应联系。因此在副作用函数中当读取一个只读属性的值时,不需要调用track函数追踪响应
为了实现这个功能,需要修改get拦截函数的实现
function createReactive(obj, isShallow = false, isReadonly = false){
return new Proxy(obj, {
get(target, key, receiver){
if(key === 'raw'){
return target
}
// 非只读的时候才需要建立响应联系
if(!isReadonly){
track(target, key)
}
...
}
})
}
这样我们就实现了readonly函数了
function readonly(obj){
return createReactive(obj, false, true)
}
但上面实现的只是浅只读——shallowReadonly
要实现深只读,还应该在get函数内递归地调用readonly将数据包装成只读的代理对象,并将其作为返回值返回。
function createReactive(obj, isShallow = false, isReadonly = false){
return new Proxy(obj, {
get(target, key, receiver){
if(key === 'raw'){
return target
}
if(!isReadonly){
track(target, key)
}
const res = Reflect.get(target, key, receiver)
if(isShallow){
return res
}
if(typeof res === 'objcet' && res !== null){
// 如果数据为只读,则调用readonly对值进行包装
return isReadonly?readonly(res):reactive(res)
}
return res
}
})
}
所以对于shallowReadonly来说,实际上只需要修改createReactive的第二个参数即可
function readonly(obj){
return createReactive(obj, false, true)
}
function shallowReadonly(obj){
return createReactive(obj, true, true)
}