首先我们知道,Vue2中实现响应式的根本是使用了Object.definePropery,在初始化的时候给data对象中的每一个属性名都添加一个对应的get和set在它读写的时候调用,但这暴露了一个问题,就是没法在对整个对象的实现监听,也就是在给一个对象添加属性或者删除一个属性的时候,因为初始化函数不会调用,所以也就不会被get和set捕获到,于是想要后续追加属性被监听到需要调用vm实例身上的set方法,但是在Vue3运用了ES6中的Proxy代理完美解决了这个问题。
Proxy是一个构造函数,是实现对一整个对象的监听而不是defineProperty的对属性名监听,在创建时传入两个参数,第一个是需要被代理的对象,第二个是代理的配置对象(至少要传一个空对象,否则会报错),而这个对象中可以除了get/set还有一个deleteProperty,会在代理对象中的属性值被删除时调用,同时代理对象中被追加一个属性的时候也会调用set,如图
![](https://i-blog.csdnimg.cn/blog_migrate/298b776e2a9851471965d36fd22eed19.png)
图中的形参target和key分别是要读取的这个对象和要读取的属性名,而value则是返回要修改的值或是新赋的值。由于是代理,没有跟原对象的直接对接,所以这里搭配了Reflect中的方法去原对象中进行操作,get返回的是读取到的值,而set和delete则是返回true/false判断是否修改成功。
![](https://i-blog.csdnimg.cn/blog_migrate/34c86b1a0bd7565e0f2549dd2c4b1646.png)
基于以上两点,实现reactive实现的第一步
function reactive (obj) {
return new Proxy(obj, {
get (target, key) {
return Reflect.get(target, key)
},
set (target, key, value) {
return Reflect.set(target, key, value)
}
})
}
然后创建一个effect(依赖)函数对其进行初始化
//首先effect需要接受一个函数并将其保存在自身方便之后调用
//全局变量activeEffect存储每一个激活状态的effect以便对其追加属性
let activeEffect
function effect(fn) {
//这里则创建一个构造函数
//经过初始化过后把这个fn保存在实例化对象上也就是_effect身上
//然后原型上拥有一个run函数主要在被调用时可以把当前的状态给保存下来 然后把最开始传入的fn的执行结果返回出去
const _effect = new ReactiveEffect(fn)
_effect.run()
}
class ReactiveEffect {
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
return this._fn()
}
}
这个时候num = 17应该没什么问题,因为effect在调用时候触发了ReactiveEffect的实例化对象身上的run方法,执行了传入的fn
const obj = reactive({num:15})
let num
effect(()=>{
//这里的obj.age会触发一次get操作
num = obj.age + 2
})
console.log(17)
但是如果再让obj.age = 60的话,num不会再有变化,因为fn没有再次调用,num的值也就没有被刷新,所以这时候就需要一个拥有以下两点功能的方法,get触发时收集所有依赖,set触发时调用所有收集到的依赖,这样所有用到该值的地方都会发生更新。
如图所示,在get和set中分别加入一个函数
function reactive (obj) {
return new Proxy(obj, {
get (target, key) {
track(target,key)
return Reflect.get(target, key)
},
set (target, key, value) {
trigger(target,key)
return Reflect.set(target, key, value)
}
})
}
添加了一个track和trigger,如下
//创建一个对象用于存储所有产生依赖的对象,
const targetMap = new Map()
//把当前的值传进去
function track(target, key) {
//根据对象的指针地址找到其对应的存储空间
let depsMap = targetMap.get(target)
//第一次depsMap没有值,则需要对其进行初始化
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
//这里根据其属性地址取出对应的容器
let dep = depsMap.get(key)
//同上,也是不存在就对其进行初始化
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
//调用这个函数是为了把当前依赖于该属性的值存储进去,如果已经有了则返回不执行
trackEffects(dep)
//这个函数是为了把当前所收集到的依赖,保存到其对应的属性容器下,也就是将一开始传入的fn函数保存在了以obj.
function trackEffects(dep) {
//这里activeEffect是先前创建的全局变量,就犹如v-for循环时给函数传入index就能准确找到触发函数的那一项
if (dep.has(activeEffect)) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
//依赖收集之后,一旦值发生改变,则会更新其被依赖的所有值
function trigger(target, key) {
//取出对象容器
let depsMap = targetMap.get(target)
//取出属性容器
let dep = depsMap.get(key)
//传入更新所有依赖于该属性的值
triggerEffects(dep)
function triggerEffect(dep){
for(const effect of dep){
//执行所有存储的依赖,如以上那个例子就是执行num = obj.age + 2
effect.run()
}
}
}
此时就实现了Vue3中reactive的雏形,后续添加还可以实现ref代理对象,computed计算属性等功能