只读和浅只读
我们希望一些数据是只读的,当用户尝试修改只读数据时,会收到一条警告信息。这样就实现了对数据的保护,例如组件接收到的 props 对象应该是一个只读数据。这是就要用到接下来我们要讨论的readonly
函数,它能够将一个数据变成只读的:
1 | const obj = readonly({ foo: 1 }) |
只读本质上也是对数据对象的代理,我们同样可以使用createReactive
函数来实现。如下面的代码所示,我们为 createReactive 函数增加第三个参数 isReadonly:
1 | // 增加第三个参数isReadonly 代表是否只读,默认为false,即非只读 |
在这段代码中,当使用createReactive
创建代理对象时,可以通过第三个参数isReadonly
指定是否创建一个只读的代理对象。同时,我们还修改了 get 拦截函数和 deleteProperty 拦截函数的实现,因为对于一个对象来说,只读意味着既不可以设置对象的属性值,也不可以删除对象的属性。在这两个拦截函数中,我们分别添加了是否是只读的判断,一旦数据是只读的,则当这些操作发生时,会打印警告信息,提示用户这是一个非法操作。
当然,如果一个数据是只读的,那就意味着任何方式都无法修改它。因此,没有必要为只读数据建立响应联系。出于这个原因,当在副作用函数中读取一个只读属性的值时,不需要调用track
函数追踪响应:
1 | const obj = readonly({ foo: 1 }) |
为了实现该功能,我们需要修改 get 拦截函数的实现:
1 | // 增加第三个参数isReadonly 代表是否只读,默认为false,即非只读 |
如上面的代码所示,在 get 拦截函数内检测 isReadonly 变量的值,判断是否是只读的,只有在非只读的情况下才会调用track
函数建立响应联系。基于此,我们就可以实现readonly
函数了:
1 | function readonly(obj) { |
然而,上面实现的readonly
函数更应该叫作shallowReadonly
,因为它没有做到深只读:
1 | const obj = readonly({ foo: { bar: 1 } }) |
所以为了实现深只读,我们还应该在 get 拦截函数内递归地调用 readonly 将数据包装成只读的代理对象,并将其作为返回值返回:
1 | function createReactive(obj, isShallow = false, isReadonly = false) { |
如上面的代码所示,我们在返回属性值之前,判断它是否是只读的,如果是只读的,则调用 readonly 函数对值进行包装,并把包装后的只读对象返回。
对于shallowReadonly
,实际上我们只需要修改createReactive
的第二个参数即可:
1 | // 深只读 |
如上面代码所示,在shallowReadonly
函数内调用createReactive
函数创建代理对象时,将第二个参数isShallow
设置为 true,这样就可以创建一个浅只读的代理对象了。
代码
至此,我们实现了深只读和浅只读,贴一下最新的代码便于查看:
1 | /** |