接着上一篇我们提到的vue2的响应式原理,我们这篇文章将聊聊vue3的响应式原理。
vue3 的响应式核心
vue3 的响应式核心就是proxy代理。它与vue2中的Object.defineProperty
作用类似,都是将数据拦截下来,也就是在设置对象属性或者读取对象属性时做一层拦截操作。但是最大的不同之处在于proxy是创建一个对象的代理,并不是在原对象上进行改变,而 Object.defineProperty
是直接在原对象上修改或定义属性。
proxy的使用
Proxy
是 ES6
中新增的一个函数,但 Proxy
本质就是一种构造器,我们要通过 new
去实现的。如
js
代码解读
复制代码
let observed = new Proxy(target, baseHandler)
它有两个参数,分别代表被代理的对象以及处理器对象,也就是进行各种操作的对象。处理器对象中包含多个方法,如:
- get(target, key, receiver):读取代理对象的某个属性时触发该操作
- set(target, propKey, value, receiver) :给代理对象的某个属性赋值时触发该操作
- has(target, propKey) :用于判断代理对象是否拥有某个属性时触发
- deleteProperty(target, propKey): 删除代理对象的某个属性时触发该操作 等等···
我们在响应式中需要用到的就是set()和get()。
get
方法有三个参数get(target, key, receiver)
分别为目标对象、键、实例对象本身
set
方法有四个参数 set(target, key, value, receiver)
,分别为目标对象、键、值、实例对象本身。
具体的用法就如下所示:
js
代码解读
复制代码
let baseHandler = { get(target, key, receiver) { console.log('读取'); let result = Reflect.get(target, key) // target[key] return isObject(result) ? reactive(result) : result // 递归 }, set(target, key, value, receiver) { console.log('修改'); return Reflect.set(target, key, value, receiver) // 将target中的key值修改为value } }
在这里需要注释一下Reflect对象,Reflect其实与我们常用的Object对象并无太大差异,它设计初衷是为了支持 Proxy
,使得 Proxy
的处理器对象能够更方便地调用标准的操作。Reflect
提供了一组静态方法,用于直接操作对象,同时保持与 Proxy
操作的一致性。具体的差异大家可以自行查看一下资料。
另外proxy也不能深层代理对象,所以仍需和Object.defineProperty
一样进行递归。但是它与之不同的是只用在用到的时候进行递归,而不是无脑的默认递归。
Proxy对象可以拦截13种不同的操作,所以它不会有Object.defineProperty
无法数组进行拦截,以及对不存在的属性进行拦截的困扰。这大大的减少了vue3的源码代码量,使之更简洁也更易懂。
vue3 响应式源码
vue3的响应式核心就是proxy,其他的与vue2较为类似,知识不需要再额外的对数组进行操作。但是在vue3的响应式中,我们需要考虑被代理后的对象不需要重复代理以及对象是否被二次代理过。。尤达大这一部分考虑进去了确实减少了很多的性能开销,因为只用被代理一次就行了。
在这个问题上用的解决方法是是WeakMap
和WeakSet
,具体的请,如下代码片段:
js
代码解读
复制代码
// 判断是否被代理了 let proxy = toProxy.get(target) if (proxy) { return proxy } // 判断代理对象是否被二次代理过 if (toRow.has(target)) { return target } // 对象代理 let observed = new Proxy(target, baseHandler) toProxy.set(target, observed) toRow.add(observed, target)
总结
vue3的响应式还是很简洁的,但是它解决了vue2响应式中存在的问题,包括不能处理数组,不能处理数组上的length属性,以及对象上不存在的属性劫持。
原文链接:https://juejin.cn/post/7412672705302495243