前言
在上一篇中,我们讲到了Vue在创建一个实例,需要完成的任务主要是初始化实例,其中对数据的响应式在初始化函数initData中的observe实现的,observe函数中,该对象首先判断是否已经是响应式,如果不是响应式,则添加一个一个值为Observer实例的ob属性。Observer类有三个属性,分别是value,dep,vmCount,在其构造函数中,我们会先判断value是数组还是对象,如果是对象,则通过walk方法遍历,并执行defineReactive函数;如果是数组,则先改变数组的原型链,指向arrayMethods,再遍历数组,对每个元素再调用observe方法(数组和对象分别处理原因参考大致了解Vue2响应式原理(一))。
接下来就defineReactive函数和Dep类展开描述
defineReactive函数
defineReactive主要通过defineProperty对属性进行getter和setter的重写,在get中收集依赖,在set中提醒依赖进行更新,以下是defineReactive实现代码。
export function defineReactive(
obj:object,
key:string,
val?:any,
customSetter?: Function | null, //自定义函数,在set中进行回调
shallow?:boolean,//是否浅遍历
){
const dep = new Dep()
//此方法返回给定对象的所有属性的信息,如果不可配置,直接return
const property = Object.getOwnPropertyDescriptor(obj,key)
if(property && property.configurable === false){
return
}
//用户是否有自定义get、set属性
const getter = property && property.get
const setter = property && property.set
if(
(!getter || setter) &&
//如果只传递了obj和key,给val赋值
(val === NO_INITIAL_VALUE || arguments.length === 2)
){
val = obj[key]
}
//对val再通过observe递归
let childOb = !shallow && observe(val,false)
Object.defineProperty(obj,key,{
enumerable:true, //可以被枚举
configurable:true,//可以配置
get:function reactiveGetter(){
const value = getter ? getter.call(obj) : val
if(Dep.target){
if(__DEV__){
dep.depend({
target:obj,
type:TrackOpTypes.GET,
key
})
}else{
dep.depend()
}
if(childOb){
childOb.dep.depend()
if(isArray(value)){
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
set:function reactiveSetter(newVal){
const value = getter ? getter.call(obj) : val
if(!hasChanged(value,newVal)){
return
}
if(__DEV__ && customSetter){
customSetter()
}
if(setter){
setter.call(obj,newVal)
}else if(getter){
return
}else if(!shallow && isRef(value) && !isRef(newVal)){
value.value = newVal
return
}else{
val = newVal
}
childOb = !shallow && observe(newVal,false)
if(__DEV__){
dep.notify({
type:TriggerOpTypes.SET,
target:obj,
key,
newValue:newVal,
oldValue:value
})
}else{
dep.notify()
}
}
})
return dep
}
在defineReactive函数中,我们主要完成了以下任务:
- 通过getOwnPropertyDescriptor获取该对象属性的所有属性,并判断是否可以对该属性进行配置
- 定义getter、setter,通过&&,如果有property,则继续执行property.get/set ,如果没有property,则返回undefined,这种用法被称为“短路求值”
- 如果用户没有传递val,则自定义val,通过obj[key]对val赋值
- 重写getter、setter如下
- getter:判断目前是否有watcher(关于watcher在后续中会提到),如果不存在则直接返回value,如果存在,则将当前的watcher加入到该value的依赖中。
- setter:判断是否需要新旧值是否相等,相等的话不用触发更新,判断用户是否有自定义的setter,有的话就调用自定义setter,如果旧值是一个ref对象,而新值不是,则把newVal赋值给旧值的value(因为是响应式数据,取值需要.value),否则直接赋值,如果newVals是对象或者数组,则通过observe递归,最后对相关的dep对象进行更新。
Dep类
在看代码之前,我们先弄清楚Dep类的作用是什么,在Vue2中,Watcher对象依赖于各个数据,一个Watcher对象可能会依赖多个数据,而一个数据也可能被多个Watcher依赖,于是提取出这种依赖,就是Dep对象。每一个数据都有一个对应的Dep对象,当一个数据被读取的时候,会调用getter函数,于是将依赖该数据的Watcher存储至该Dep对象中便于管理。Dep和数据一一对应,Dep和Wather的关系是多对多,下面对Dep类的代码简单分析一下。
let uid = 0
const pendingCleanupDeps: Dep[] = []
export interface DepTarget extends DebuggerOptions {
id: number
addDep(dep: Dep): void
update(): void
}
export default class Dep{
static target?: DepTarget | null //当前正在执行的Watcher对象
id: number //dep的唯一标识
subs:Array<DepTarge | null> //依赖当前dep的所有Watcher对象
_pending = false // 标识当前Dep对象中是否有Watcher对象被设置为null,subs数组等待被cleanupDeps
constructor(){
this.id = uid++
this.subs = []
}
//向依赖数组中添加一个Watcher对象
addSub(sub:DepTarget){
this.subs.push(sub)
}
removeSub(sub){
this.subs[this.subs.indexOf(sub)] = null
//当前有Watcher对象被清除了
if(!this._pending){
this._pending = true
//将Dep实例加入待清除的pendingCleanupDep数组中,这个一个Watcher对象用来记录依赖的数组
pendingCleanupDep.push(this)
}
}
//如果当前存在正在计算的Watcher对象进行关联,将该Dep对象,通过addDep方法添加到Watcher对象的deps属性中
depend(info){
if(Dep.target){
Dep.target.addDep(this)
if(__DEV__ && info && Dep.target.onTarck){
Dep.target.onTrack({
effect:Dep.target,
...info
})
}
}
}
notify(info){
const subs = this.subs.filter(s => s) as DepTarget[]
if(__DEV__ && !config.async){
subs.sort((a,b)=>a.id - b.id)
}
for(let i = 0; i < subs.length; i++){
const sub = subs[i]
if(__DEV__ && info){
sub.onTrigger && sub.onTrigger({
effect:subs[i]
...info
})
}
sub.update()
}
}
}
Dep类中的属性
首先我们对Dep类的属性方法有一个大致的了解:Dep类有target,id,subs,pending四个属性
- target是静态变量,是定义在Dep类上的,而不是dep实例上,这意味着所有的Dep实例都共享这一个属性target,该属性代表的是目前正在计算的Watcher对象(Watcher对象一时间只能有一个,可以理解为不管什么时候,都只有一个正在运行/计算/活跃的Watcher对象)
- id是唯一标识
- subs是依赖该Dep对象的所有Watcher对象
- pending是一个清除标识符,在removeSub方法中会具体说到
Dep中的方法
接下来聊一下Dep中的方法:addSub,removeSub,depend,notify
- addSub方法相对较为简单,传入参数Watcher对象,通过对subs数组push,将这个Watcher对象加入到依赖数组中即可
- removeSub方法,目的是将当前Watcher对象移出依赖数组,由于直接修改subs数组会造成性能上的问题,所有在修改subs数组时,并不是直接删除,而是通过索引找到该Watcher对象在subs数组中的位置并赋值为null,然后将pending标识符置为true,然后将当前Dep对象加入到pendingCleanupDeps全局数组中,在后续统一对这个数组遍历,将数组中dep元素的sub为null的值删除,然后把pending置为false。
- depend方法,将当前的Dep对象添加到正在计算的Watcher对象的deps属性中。如果是开发模式下,通知开发者当前正在进行的追踪操作。
- notify方法,首先对当前的subs数组过滤值不为null的Watcher对象,遍历过滤后的数组,然后用过update方法,通知依赖该dep的Watcher对象进行更新。
结语
在本章中,我们大致了解了defineReactive以及dep类,这部分是Vue2响应式的核心原理,至于其中还提到了很多细节的函数,并未在这里一一说明,后续会慢慢补充,下一章将会对Watcher对象进行了解,再将Dep对象与Watcher对象做出更深刻的理解,并了解render函数。