- 作者:老汪软件技巧
- 发表时间:2024-12-19 11:07
- 浏览量:
书接上文一篇文章,加深对vue响应式的理解
Vue3的响应式
大家或多或少也都听过这么一个构造方法Proxy,了解Vue3的响应式之前,我们有必要了解一下这个工具,这里推荐一下阮一峰老师的文章,。
简单的概括一下,Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。在与操作对象之间设置了一层间隔,我们对其操作,只需要与这层代理交互,就好比老板的秘书,它接受两个参数:target,handler,
const p = new Proxy(target, handler)
了解了Vue2响应式的基础实现,相信可以更好的去理解Proxy给我们带来的便利。
实现
到这里是不是有点像数据劫持的味道了,那么,不一样来了
我们可以看见get和set的用法,既然是代理对象属性的读取,那么我们是否可以直接return target[key]呢?所以这里不会无限递归访问对吧。
那么这里,我们还可以优化一下,如果你有看阮一峰老师的文章,在Proxy下面,不知道你是否好奇去看一了下,由于Object对象身上有了太多的方法,于是打造了一个新的API,作为替身,将来会把需要在Object身上新增的方法添加在Reflect身上,也就是说,Reflect和Object一样使用就行。另外,因为Object身上的某些方法并不优雅甚至报错会造成程序终止,对于程序员并不友好,比如我们之前用到的defineProperty在无法定义属性时会抛出一个错误,而我们使用Reflect则只会返回false。更多的,还需要我们多加地去阅读。
那么我们写到这里,如果是这样,会输出什么呢?
const data = {
a: 1,
b: {
e: 2,
f: 3
},
c: [1, 2, 3]
}
const proxy = reactive(data)
console.log(proxy.b.e);//get 2
看来,和数据劫持一样,依然需要递归,那么两者又有什么区别呢?
当然有,我们可以这样写:
那么区别来了,在数据劫持中,我们是默认递归,无论是否访问,都会递归地去劫持,而在这里,我们只是按需递归。同时,我尝试去验证数据劫持中遗留的问题,都解决了。我们不必再去重写数组的方法等等一些繁琐的操作。
那么问题又来了,既然这样,我们每次访问到同一个地方,都会去创建一个新的代理对象,或者,可能由于疏忽,会多次的代理同一个对象,很不优雅。
所以,尤大创建了两个数据结构来存储, new WeakMap()用来存放原对象:代理对象,new WeakSet() 用来存放代理过的对象所以,在我们进行代理之前,进行判断,如果WeakMap已存在该对象代理过的,就可以拿到第一次代理的对象,如果没有,则判断是否WeakSet中存在,如果存在,说明当前就是第一次代理过的对象,否则,进行代理。
const toProxy = new WeakMap()
const toRow = new WeakSet()
const isObject = (target) => {
return typeof target == 'object' && target != null
}
const handler = {
get(target, key, receiver) {
console.log('get',target[key]);
let reult = Reflect.get(target, key, receiver)
return isObject(reult) ? reactive(reult) : reult
},
set(target, key, value, receiver) {
console.log('set',value);
updateView()
return Reflect.set(target, key, value, receiver)
}
}
const updateView = () => {
console.log('updateView');
}
const createReactiveObject = (target) => {
if (!isObject(target)) {
return target
}
if(toProxy.has(target)){
return toProxy.get(target)
}
if(toRow.has(target)){
return target
}
let proxied = new Proxy(target, handler)
toProxy.set(target,proxied)
toRow.add(target)
return proxied
}
const reactive = (target) => {
return createReactiveObject(target)
}
数据劫持中的遗留问题,一一解决了,并且代码更为的优雅。如果你读到这里,希望能够给自己几分钟整理一下整个响应式的构建过程,当然,本文只是简单的实现了数据的响应式,并不完善,还有很多边缘操作需要兼顾。
重新整理,本人也有收益,学无止境,温故而知新。