「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
一、前情回顾 & 背景
在上一篇中,我们基本上已经交到清楚 initProps 都发生了什么,其核心就在于通过 defineReactive 将 vm.$options.propsOptions 上我们配置的 props 都变成响应式数据,核心在于 observe 方法,而observe 方法的核心是 Observe 类;
此外,在实现响应式数据的过程中,有一个时至今日我们一直在提及但从未谋面的狠人,他就是 Dep,我们口口声声说的依赖收集、派发更新就是他的看家本事了,今天他来了。
一、、Observer
类的位置:src/core/observer/index.js -> class Observer
类的作用:创建观察者类的实例,其构造函数接收一个被观察对象 value,创建实例则会将观察者实例作为属性 __ob__ 的值附加到 value 自身上,即 value.__ob__ ;
观察者对象将会把目标对象 value 的属性转换为 getter 和 setter 用于收集依赖和派发更新。
在构造函数中主要做了以下工作:
- 给观察者实例上初始化一个
Dep实例,这个就是用来做依赖收集用的; - 在被观察的
value对象上添加__ob__属性,值是this,即观察者对象本身; - 如果判断
value是数组,则重写数组原型上的操作数组的方法,用以实现数组的响应式,接着调用this.observeArray(value)将数组项也变成数据响应式,这就是为啥数组没有getter和setter照样能有响应式的根本原因; - 如果
value是个对象,则调用this.walk(value)方法遍历,把value中所有子属性及后代属性都变为响应式,这也解释了为啥observe里面没有见到递归的defineReactive调用,这一步已隐式的在这里完成了
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
// 实例化一个 dep
this.dep = new Dep()
this.vmCount = 0
// 在 value 对象上设置 __ob__ 属性
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
// 有 __proto__ 属性,通过原型增强
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// value 为对象,递归为对象的每个子属性设置响应式
this.walk(value)
}
}
// 遍历对象上的每个 key,为每个 key 设置响应式
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// 这里相当于定义响应式
defineReactive(obj, keys[i])
}
}
// 遍历数组,为数组每一项设置观察,处理数组元素为对象的情况
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
1.1 protoAugment 方法
方法位置:src/core/observer/index.js -> protoAument
方法作用:接收 target 和 src 参数,用于修改 target 对象的 __proto__ 属性指向,即就修改原型对象的指向,在 new Observer 的时候,当 value 是数组时,通过修改 value 的 __proto__ 指向一个重写过数组方法的对象;
function protoAugment (target, src: Object) {
target.__proto__ = src
}
1.2 copyAugment
方法位置:src/core/observer/index.js -> copyAugment
方法作用:接收 target、src、keys,向 target 扩展 keys 中的 key,key 对应的值是 src[key];
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
可以看到,前面是先判断对象有没有 __proto__ 属性,如果有的话,就 protoAument 否则才 copyAument。为啥要这么处理?这是因为 __proto__ 不是个标准属性,很可能有的浏览器就没有实现,比如IE,所以就需要 copyAument 来增加实现数组的响应式。
二、Dep
类的位置:src/core/observer/dep.js -> class Dep
类的作用:当响应式数据读取时,收集依赖,前面有说过每个响应式的数据,一个 key 都会有一个 dep,而子属性也会有自己独立的 dep,所谓收集依赖就是收集 watcher,即哪个 watcher 读取了这个 key。当响应式数据更新时派发更新,所谓派发更新就是调用 watcher.update() 方法,使之重新求值;
export default class Dep {
static target: ?Watcher; // Dep.target 是个 Watcher 类型的值
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 在 dep.subs 中push watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
}
// 遍历 dep 中存储的 watcher,执行 watcher.update()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
2.1 Dep.prototype.depend
该方法用于向 watcher 中增加 增加 dep,同时向 dep 中增加 watcher;在前面 defineReactive 方法中,最后的 Object.defineProperty 的 getter 中调用了该方法,
Dep.target 是 Dep 类的一个静态属性,值为 Watcher,在实例化 Watcher 时他会被设置,实例化 Watcher 时会执行 new Watcher 时 pushTarget(this),该方法此时会为 Dep.target 赋值当前的 Watcher 实例;
export function defineReactive (params was ignored) {
// 实例化 dep,一个 key 一个 dep
const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// get 拦截对 obj[key] 的读取操作
get: function reactiveGetter () {
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
}
})
}
2.3 Dep.prototype.notify
notify 方法就是派发更新的,派发更新就是遍历 dep 收集的这些 watcher ,调用每个 watcher.update() 就可以了;
前面调用 defineReactive 中最后的 Object.defineProperty 法中中的 setter 就调用了 notify
export function defineReactive (params was ignored) {
// 实例化 dep,一个 key 一个 dep
const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
},
set: function reactiveSetter (newVal) {
dep.notify() // 派发更新
}
})
}
三、总结
到这里我们已经说完了全部的 initProp 的过程,现在我们回顾一下:
- 初始化
vm._props属性, - 然后
for in遍历vm.$options.propOptions对象,这里面放的都是我们new Vue()时传递的props对象, - 在遍历时,通过
validateProp方法获取每个key的默认值,如果这个值不再propsData中,在此过程中会调用observe(val)方法对默认值进行观察,将其转换成响应式数据结构:-
3.1
observe方法内部是创建Observe的实例,而Observer的构造函数执行过程:- 3.1.1 会给
val实例化dep属性 - 3.1.2 并且给
val添加__ob__属性,值就是Observer实例自身 - 3.1.3 如果 val 是数组,则通过
protoAument/copyAument覆写数组方法实现数组响应式,接着调用this.observerArray实现数组子项的响应式 - 3.1.4 如果
val是对象,则this.walk(),即调用Observer原型方法walk遍历val,其核心是递归调用defineReactive()方法,将对象的子属性等深层次的子属性都变成响应式
- 3.1.1 会给
-
3.2 返回
new Observer创建的实例,当然也会判重,如果val已经是Observer实例了则直接范返回
-
- 调用
defineReactive(obj, key, value)方法,通过Object.defineProperty()方法,拦截属性的读取和设置,在读取时进行依赖收集,在设置时派发更新:- 4.1 实例化
Dep的实例dep = new Dep() - 4.2 获取
obj[key]原有的属性描述对象,然后从描述对象获取getter和setter - 4.3 调用
childObj = observe(val)方法,observe内部就是实例化Observer类,并会归调用defineReactive方法,这里算是 defineReactive 方法的隐式递归调用,实现所有嵌套子属性的响应式 - 4.4 调用
Object.defineProperty方法,重新定义obj[key]的get和set:- 4.4.1 get 时:
dep.depend(),childObj.dep.denpend(),这就是引用到obj.key和obj.key.childKey时,都能被监听到的原因 - 4.4.2
set时,重新observe新值,使得新值也是响应式的,最后dep.notify()即派发更新
- 4.4.1 get 时:
- 4.1 实例化