引
从这篇文章开始阅读Vue2响应式源码,后续还会阅读Vue3响应式的源码来进行比较,特此记录。
function initState
响应式的起点在initState函数中。
initState函数接受传递进来的vue实例,在函数中主要是对实例上的props、methods、data、computed、watch做了不同的处理。
而在对data的处理中,调用了observe方法,observe就是响应式的开始。
//src\core\instance\state.ts
export function initState(vm: Component) {
// vm:vue实例
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
// Composition API
initSetup(vm)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
//initData中也调用了observe
initData(vm)
} else {
const ob = observe((vm._data = {}))
ob && ob.vmCount++
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initState方法不做过多展开,之后再来填坑,接下来步入正题。
function observe
响应式源码主要在src\core\observer文件夹下,这篇文章来阅读其中的index.ts文件。
该文件主要实现了observe方法、Observer类、defineReactive方法。
先来看看响应式的入口observe。
//响应式入口
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
//__ob__属性是一个标记,为了避免重复创建响应式对象。class Observer中会讲到
//拥有__ob__属性则直接返回,避免重复创建
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
if (
//需要响应式
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
//是数组或普通对象
(isArray(value) || isPlainObject(value)) &&
//判断对象是否可扩展
Object.isExtensible(value) &&
//不是无需响应的对象
!value.__v_skip /* ReactiveFlags.SKIP */ &&
//不是响应式对象
!isRef(value) &&
!(value instanceof VNode)
) {
// 为value创建Observer响应式对象
return new Observer(value, shallow, ssrMockReactivity)
}
}
总结来说,observe方法主要的作用是判断传入的value是否需要做响应式处理,核心是new Observer。
class Observer
//可观测对象Observer类
export class Observer {
//记录依赖关系的容器
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
//创建dep容器
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
//标记自己,避免重复创建,__ob__就是Observer对象
def(value, '__ob__', this)
//判断是对象还是数组
if (isArray(value)) {
//是数组
//mock?
if (!mock) {
//__proto__是否可用 有些浏览器不支持
if (hasProto) {
//覆盖__proto__
//arrayMethods是vue2重写后的Array.prototype,使原生数组方法引起的数组变化能够被监听
;(value as any).__proto__ = arrayMethods
} else {
//遍历数组方法,覆盖value数组上的数组方法
//def方法内使用Object.defineProperty覆盖数组方法
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
//是否为浅式响应式
if (!shallow) {
//数组内每一个元素都创建observer
this.observeArray(value)
}
} else {
//是对象
//遍历对象,对每个属性执行defineReactive方法
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
//Observe a list of Array items.
observeArray(value: any[]) {
//遍历数组元素,调用observe
//为了在数组嵌套或数组内有对象的情况下,能够深度监听
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
Observer类的构造函数中,对value的类型做了区分。
若value是数组,则只是用vue2框架重写的数组方法替换了数组的原生方法,这也就解释了为什么直接用数组下标修改数组不会触发响应式。
若value是对象则是遍历属性,调用defineReactive方法。
function defineReactive
//定义响应式属性
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean,
observeEvenIfShallow = false
) {
//创建dep容器
//这里的dep用于收集obj.key的依赖 //闭包
//Observer类中的dep收集obj的依赖
const dep = new Dep()
//获取obj对象上的key属性的配置对象
const property = Object.getOwnPropertyDescriptor(obj, key)
//属性存在 && 属性不可被修改
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
//获取原生getter setter
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
//val是obj.key,如果val是对象,那么childOb就是一个Observer实例
//有了孩子的Observer,当孩子改变时我们就能知道了
let childOb = shallow ? val && val.__ob__ : observe(val, false, mock)
//在obj对象上覆写key属性的配置对象
Object.defineProperty(obj, key, {
//可被枚举
enumerable: true,
//可修改
configurable: true,
//重写getter
get: function reactiveGetter() {
// 原生方法获取value
const value = getter ? getter.call(obj) : val
//Dep.target是watcher对象
//Dep.target是变化的 根据当前解析流程不停地指向不同的watcher
//关于watcher的实现细节之后写
if (Dep.target) {
//环境判断
//主要是调用了dep的depend方法
//depend方法中调用watcher的addDep方法
//addDep方法又会调用dep的addSub方法
//最终目的是向dep容器存入watcher实现依赖的收集
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
// childOb有值 说明该属性是数组或对象
if (childOb) {
//子元素的依赖收集 即将同一个watcher存入自身属性的dep和子元素的dep
//childOb.dep这里的dep是子元素的Observer实例上的dep
childOb.dep.depend()
//是否为数组
if (isArray(value)) {
//遍历数组 调用数组内每一个元素的depend
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
//对getter的理解我们可以假设一个场景,假设变量x的值依赖于data对象上的a属性,
//在vue第一次获取x的值的时候就会调用data.a的getter,
//这是watcher就像是一条登记信息,dep是一个登记本,在data.a维护的一本登记本上写下了x的信息,
//这样在data.a变化时,就可以通过dep登记本上的信息,来通知x更新。
//其实为了避免重复收集以及方便依赖重置,watcher也会维护一个数组用来记录自己依赖的dep。具体细节在watcher源码中讲
//重写setter
set: function reactiveSetter(newVal) {
//原生方法获取value
const value = getter ? getter.call(obj) : val
//判断值是否变化 无变化直接返回
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
//有setter则执行原生setter
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
//非浅式响应式 && value是响应式对象 && newVal不是响应式对象
value.value = newVal
return
} else {
val = newVal
}
// 如果newVal是对象,那么对newVal执行observe方法进行响应式处理
childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock)
//调用dep.notify
//notify函数内:遍历dep容器内所有watcher ,用每一个watcher的update方法
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
}
//setter主要做的就是更新值然后调用notify方法派发更新
})
return dep
}
到这里,observer文件夹内index文件的内容就讲完了,vue重写了getter/setter实现了依赖收集与更新,从而完成响应式。
这里绕不开的两个类是Dep和Watcher,下一篇就来看看这两个类的实现。