持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 9 天,点击查看活动详情
5.响应式原理-3.observe
start
- 开始阅读 observe 相关的源码。
- Vue 项目针对响应式相关的代码,都放在了
\src\core\observer目录下。
1. observe
\src\core\observer\index.js
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
/**
* 1.
* 尝试为一个值创建一个观察者实例
* 如果成功观察,返回新的观察者
* 如果值已经有一个,返回现有的观察者,。
*/
// 2. 调用的时候 observe(data, true /* asRootData */);
export function observe(value: any, asRootData: ?boolean): Observer | void {
// 3. value 需要处理的数据,asRootData 作为根数据
// 4. 如果不是对象,如果是虚拟DOM。直接 return。 (这里需要注意,虚拟dom没有做响应式节约性能)
if (!isObject(value) || value instanceof VNode) {
return
}
// 5. 定义一个变量 ob ,类型是Observer或者void
let ob: Observer | void
// 6. hasOwn:value自身的属性上是否有 '__ob__' , 而且 value.__ob__是 Observer的实例
// 简单来说:已经是响应式的,直接复用。
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 7. 需要监听;不是服务端渲染;是数组 或者 普通对象 ;可扩展;_isVue为false
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 8. new Observer(value),传入的值为 我们的data
ob = new Observer(value)
}
// 如果是asRootData,添加计数vmCount
if (asRootData && ob) {
ob.vmCount++
}
// 10.返回 ob
return ob
}
整个代码看下来:
- 判断传入的值不是对象,是虚拟 dom, 直接 return;
- 判断对象上是否已经有
__ob__属性,而且__ob__上存储的是 Observer 实例,直接复用; - 满足一些逻辑校验,开始
new Observer();
总结一下,observe 主要是做逻辑过滤,然后开始 new Observer()。
2. Observer
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
/**
* 1.
* 每个被观察对象附加的观察者类
* 对象。一旦连接上,观察者将转换目标
* 对象的属性键到getter/setter
* 收集依赖关系并分发更新。
*/
export class Observer {
// 2. class直接定义变量, 相当于 function Observer(){} ;Observer.value;Observer.dep; Observer.vmCount;
value: any
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(value: any) {
// 3.存储 value
this.value = value
// 4.创建一个依赖收集者
this.dep = new Dep()
// 5. 计数 把这个数据当成根data对象的实例数量
this.vmCount = 0
// 6. 给 data 设置一个 __ob__ 属性,值为 Observer 实例
// 这个地方可以知道为什么我们 Vue中的数据,打印出来会带有 '__ob__'
def(value, '__ob__', this)
// 7. 如果是数组 (数组最后再讲,先说对象)
if (Array.isArray(value)) {
// 7.1 可以使用对象的 __proto__ 属性
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
// 7.2 不可以使用对象的 __proto__ 属性
copyAugment(value, arrayMethods, arrayKeys)
}
// 7.3执行 observeArray
this.observeArray(value)
} else {
// 8. 其他情况,对象
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
/**
* 9.
* 遍历所有属性并将其转换为
* getter setter。此方法只应在以下情况调用
* 值类型为Object。
*/
walk(obj: Object) {
// 10. 调用对象的属性,循环执行 defineReactive(对象, 对象的属性名)
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
// 11.观察Array项
observeArray(items: Array<any>) {
// 12. 遍历数组的每一项,全部都observe一下。
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Observer 是一个类,使用 new Observer的时候,会在 value 上添加属性 __ob__,存储 Observer 的实例.
需要注意下,new Observer 的时候,在__ob__中存储了 dep 属性。对应的源码this.dep = new Dep();
然后判断 value 是 对象还是数组,分别做不同的处理。
- 对象:
this.walk=>defineReactive; - 数组:
protoAugment/copyAugment=> 遍历每一项observe();
数组的情况后续再说,本节先说说对象的情况
对象的情况主要是遍历对象的每一个属性,对每一个属性都执行一次 defineReactive(obj, keys[i])
defineReactive(对象, 对象的属性)
defineReactive
/**
* Define a reactive property on an Object.
*/
// 1. 在对象上定义响应式属性
export function defineReactive(
obj: Object, // 传入的 对象:
key: string, // 对象的 属性;
val: any, //对象的属性值,在没有 getset的时候,直接返回对应的值。
customSetter?: ?Function, // 自定义 setter
shallow?: boolean // 是否是 浅层的响应式
) {
// 2. new Dep() , 定义个对象用来 收集依赖。
const dep = new Dep()
// 3. Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。 简单来说,拿到这个属性的配置。
const property = Object.getOwnPropertyDescriptor(obj, key)
// 4. 如果配置存在,如果 configurable===false (该属性不可修改),直接 return
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 5. 满足预定义的getter/setter
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
// 6. 如果:没有getter或者有setter;而且传入的参数长度为2。设置第三个参数 val为
val = obj[key]
}
// 7. 不是浅层的,监听 val; childOb是布尔值
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可以枚举
configurable: true, // 可以配置
// 8.定义 get
get: function reactiveGetter() {
// 8.1 拿到真实的值
const value = getter ? getter.call(obj) : val
// 8.2 收集依赖
if (Dep.target) {
dep.depend()
// 8.3 子对象
if (childOb) {
childOb.dep.depend()
// 数组处理
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// 9.定义 set
set: function reactiveSetter(newVal) {
// 9.1 真实的值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 9.2 `newVal === value` 值没有改变的时候不触发后续逻辑; (newVal !== newVal && value !== value) 这是什么意思? 防止 NaN == NaN (false) https://github.com/vuejs/vue/issues/4236
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
// 9.3 自定义 Setter
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
// 9.4 对于没有setter的访问器属性
if (getter && !setter) return
// 9.5 有 setter 走 setter
if (setter) {
setter.call(obj, newVal)
} else {
// 9.6 没有则直接赋值给 val
val = newVal
}
// 10. 处理子元素
childOb = !shallow && observe(newVal)
// 11. 通知订阅者。
dep.notify()
},
})
}
讲下defineReactive的主干逻辑。 利用Object.defineProperty(),给对象的属性设置了 get set,实现数据劫持;
在闭包中,定义了一个 对象 dep,收集和通知依赖。
原本我们获取或设置一个对象的属性,触发的都是默认的逻辑(获取就获取了,没有添加自定义的逻辑)。通过Object.defineProperty(),给对象配置 get set 可以在获取或设置对象属性的时候,自定义一些操作。
Object.defineProperty()相关内容可以参考 MDN。- 说实话,我一直在想 加 get set 有什么用? 还不是一样,获取数据,设置数据?
- 到后来我才明白,在 get set 中除了原本的获取数据,设置数据,我们还可以加入我们自己想要定义的逻辑。
- 例如上述代码的
dep.depend(),dep.notify();
收集和通知依赖
谁来收集? dep; 收集了谁? watcher; 先暂时有一个印象,后续单独研究;
当然 有些对象不仅仅只有一个层级,对于多个层级的对象,利用let childOb = !shallow && observe(val) 做了处理。
小tips
observe自身有校验,不是对象的不会继续处理。 这种递归的思路,就能实现对象的所有层级都会被处理。
思考
- 我们传入的 data 为什么会多一个
__ob__属性?
- 在处理我们传入的配置项
data时,会observe(data),随即会new Observer。 - 在
Observer的constructor中 会执行def(value, '__ob__', this),也就是会把Observer实例绑定到data的__ob__上。 - 这个地方存储
Observer实例方便后续使用,后续会提到
- Observer 处理 data 会区分数组和对象?
- 番茄其实很早就听说过,Vue.js 处理数据成响应式的,是区分对象和数组的。
- 这里查看源码就得到了验证。
- 为什么要区分对象和数组,为什么要分开处理,下一节看完数组的处理方式在细说。
def方法
def方法是一个后续经常会见到的方法,这里详细说明一下
/**
* Define a property.
* 定义一个属性
*/
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable, // 是否可枚举
writable: true, // 可写
configurable: true, // 可改变可删除
})
}
总结
- 本节主要是阅读了 Vue.js 针对对象的响应式处理的方式。
- 了解到 Vue2 响应式原理的底层
Object.defineProperty()