为了让数据有响应式,就需要在读写数据的时候能做一些操作,比如,写入数据时让页面渲染,如何直接使用obj.xx这样读取数据是没有办法在读取时执行操作的,所有就需要把写数据改成set函数,这时在set中就可以调用方法来渲染数据,那么如果使用Object.definePropertity,就需要对一个对象遍历多次来变成响应式对象,而且之后在这个对象中新增加的属性是没有响应式的,因为没有把这个属性的读写变成get和set,

在vue2中通过observe方法来观察一个对象,把它变成响应式对象,observe方法会把传进来的对象遍历,每一个属性执行defineReactive方法来使他具有响应式,当一个属性的值是对象时,递归调用observe来

而如果使用proxy,就不用循环遍历对象,当对一个proxy代理对象进行操作时

在vue3中,当我们用ref函数声明一个变量时,参数若是基础类型,例如

1
const count = ref(0)

然后在vue的源码中会调用 createRef

1
2
3
function ref(value){
return createRef(value, false)
}

这里的第二参数,意思是shallow,(shallow 是干嘛的)[https://chatgpt.com/share/673ed4f4-d3b0-800b-a80f-409e87694cf7]
在createRef函数中会判断value是否已经是Ref类型的数据,如果已经是Ref类型则直接返回这个值,否则 return new RefImpl(value, false)

1
2
3
4
5
6
7
8
9
10
function isRef(r) {
return r ? r["__v_isRef"] === true : false;
}

function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}

在RefImpl函数中,每次创建的实例都会有一个dep属性,它用来在变量被读写时做一些操作,就像上写说的我们要在响应式变量被读取时记录下来,被修改时触发一些要执行的函数,所以在RefImpl构造函数中有这样一行代码,

1
this.dep = new Dep();

同时这里也是包装响应式变量的地方,所以在这里也会有一些变量类型的标识。
总结一下这里的结构就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RefImpl{
constructor(value, isShallow){
this.dep = new Dep();
this._rawValue = isShallow ? value : toRaw(value);
this._value = isShallow ? value : toReactive(value);
this["__v_isRef"] = true;
this["__v_isShallow"] = isShallow;
}
get value(){
// 在get中追踪依赖
...
this.dep.track()
}
set value(){
// 在set中触发依赖
this.dep.trigger()
}
}

在创建每一个vue的component实例时,会有一个scope属性,scope中有effect属性,有run,stop 等方法,effect是要收集的响应式数据

1
scope = new EffectScope(true) // 创建一个与上下文分离的scope

这个scope在instance上面

创建完实例,执行setup方法时,当创建响应式变量时,每一个响应式变量实例都会创建dep实例,还有set中调用dep实例的trigger,get中调用dep实例的track,

然后在setup执行到watchEffect方法时,会把watch监听的响应式变量转换成getter方法,(这个getter方法会通过 new ReactiveEffect(fn) 转换成一个effect)并把这个effect放到上面提到的scope的effect数组中,此时还会通过一些列判断把当前effect放到全局变量activeSub上,然后执行 effect.run 方法,在run的时候就是触发了getter,此时就会触发track,在track中会把全局变量activeSub也就是当前的effect和dep通过Link类连接起来,这是一个双向链表,每一个节点都有sub和dep,(sub 就是 effect,相当于是订阅了某个响应式变量),effect中还有一个属性是scheduler,它是一个方法,会在适当的时机调用watchEffect的callback,此时就形成了响应式,在setter中可以通过dep拿到sub,也就是effect,这些effect会被推到一个job数组中被批量调用