响应式处理入口
Observer
函数定义在src/core/observer/index.js
中,首先,我们来看一下这个函数的源码:
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 判断value是否是对象
if (!isObject(value) || value instanceof VNode) {
// 如果不是一个对象或者是vNode的一个实例,则直接返回,不需要做响应式处理
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 如果value有__ob__(observer对象)属性,则令ob为value.__ob__,并返回ob
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 创建一个Observer对象
ob = new Observer(value)
}
// 如果是根数据
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
- 该函数会返回一个
Observer
的实例,参数有两个,一个为value
(在初始化时传入的就是在Vue
组件中定义的data
),一个为asRootData
(来判断当前data
是否为根数据) - 如果当前传入的
value
不是一个对象或者是一个vNode
的实例,则直接返回不需要进行响应式处理 - 如果
value
有__ob__
(observer
对象)属性,即之前做过响应式的处理,则令ob
为value.__ob__
,并返回ob
- 判断
value
是否可以进行响应式处理,如果可以创建一个Observer
对象
Observer类
- 类文件目录:
src/core/observer/index.js
- 上面有说过
observe
函数,会返回一个Observer
实例,那么接下来看一下Observer
类的定义 - 将实例挂载到观察对象的
__ob__
属性上,且该属性是不可以枚举的 - 对数组进行响应式处理
- 通过
walk
方法,对对象进行响应式处理,将目标对象转换为settter/gettter
getter/setterd
的作用是用来收集依赖于派发更新
export class Observer {
value: any; // 观测对象
dep: Dep; // 依赖对象
vmCount: number; // 实例计数器 number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0 // 初始化实例的vmCount为0
def(value, '__ob__', this) // 将实例挂载到观察对象的__ob__属性,并且__ob__属性是不可枚举的
// 数组的响应式处理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 为数组中的每一个对象创建一个observer对象
this.observeArray(value)
} else {
// walk会遍历对象中的每一个属性,转换成setter/getter
this.walk(value)
}
}
defineReactive(响应式处理)
- 函数文件目录:
src/core/observer/index.js
- 上面提到过通过
walk
方法,可以对象进行响应式处理,那么在walk
方法中,则是调用的defineReactive
,defineReactive
方法则是为一个对象定义响应式属性,下面具体来看一下这个方法的实现: - 1、判断当前是否存在且是否可配置,如果当前属性存在,且不可配置,那么直接返回
- 2、获取提供的预定义的存取器函数
- 3、判断是否递归观察子对象,并将子对象属性转换为
getter/setter
- 4、
get
:如果预定义的getter
存在,则value
等于getter
调用的返回值,否则直接赋予属性值,同时在get
中也会进行依赖收集 - 5、
set
:- 首先会先获取旧值,如果旧值与新值相等或者两个值都为
NaN
则返回; - 如果有
getter
没有setter
则返回; - 如果有
setter
,则执行setter
- 如果没有
getter
且没有setter
,则令val = newVal
- 如果新值是对象,则会将新值再转为为
getter/setter
- 最后在
set
中还会进行派发更新
- 首先会先获取旧值,如果旧值与新值相等或者两个值都为
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean // shallow是否需要深度监听这个对象
) {
const dep = new Dep() // 创建依赖对象实例
// 获取obj的属性描述符对象
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
// 如果当前属性为不可配置,那么直接返回
return
}
// 提供预定义的存取器函数
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 判断是否递归观察子对象,并将子对象属性转换为getter/setter,返回子观察对象
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 如果预定义的getter存在,则value等于getter调用的返回值
// 否则直接赋予属性值
const value = getter ? getter.call(obj) : val
// 如果存在当前依赖目标,即watcher对象,则建立依赖
if (Dep.target) {
dep.depend()
// 如果子观察目标存在,建立子对象的依赖关系
if (childOb) {
childOb.dep.depend()
// 如果属性是数组,则特殊处理收集数组对象依赖
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 返回属性值
return value
},
set: function reactiveSetter (newVal) {
// 如果预定义的getter存在,则value等于getter调用的返回值
// 否则直接赋予属性值
const value = getter ? getter.call(obj) : val
// 如果新值等于旧值,或者新值旧值为NaN,则不执行
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
// 如果没有setter,则直接返回
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新值是对象,观察子对象并返回子的observer对象
childOb = !shallow && observe(newVal)
// 派发更新(发布更改通知)
dep.notify()
}
})
}
依赖收集
-
上面有提到过在
defineReactive
中的get
里会进行依赖收集,下面具体分析一下依赖收集的过程 -
可以看到只有当
Dep.target
为true
的时候,才会进行依赖收集,那么就先从Dep.target
入手, -
首先我们先找到
mountComponent
函数,在执行mountComponent
函数时,会创建一个Watcher
实例,下面是Watcher
类中的get
方法// src/core/oserver/watcher.js get () { pushTarget(this) let value const vm = this.vm try { // 初始化时,getter就是updateComponent函数 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
-
1)找到
src/core/oserver/watcher.js
看一下实例的创建,在创建实例时,会执行Watcher
类中get
方法,get
方法中会调用pushTarget
方法,pushTarget
方法,会使得Dep.target = target
,而这个target
就是Watcher
实例,此时Dep.target
就是Watcher
实例 -
2)在执行完
pushTarget
后,就会执行this.getter
,this.getter
就是updateComponent
函数,该函数会生成虚拟DOM
,在进行取值的时候,就会触发属性的get
方法,继而执行get中的依赖收集,下面是get
进行依赖收集的代码if (Dep.target) { dep.depend() // 如果子观察目标存在,建立子对象的依赖关系 if (childOb) { childOb.dep.depend() // 如果属性是数组,则特殊处理收集数组对象依赖 if (Array.isArray(value)) { dependArray(value) } } }
-
3)上面由于
Dep.target
为true
,那么就会执行dep.depend()
,这里的dep
就是在执行defineReactive
函数时,会new
一个Dep
的实例,而这个Dep
就是依赖收集的容器,它会记录那些Watcher
依赖自己的变化 -
4)在文件
src/core/oserver/dep.js
中找到Dep
类的定义,下面可以看到在depend
函数的定义,在dep
函数中,会通过调用Watcher
实例中的addDep
方法将自己添加观察者的依赖中// src/core/oserver/dep.js depend () { if (Dep.target) { Dep.target.addDep(this) } }
// src/core/oserver/watcher.js addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
-
5)最后,在
addDep
函数中,会通过调用Dep
实例上的addSub
方法,将当前的Watcher
实例收集到dep
中的subs
中,以完成依赖收集 -
整个收集流程大概如下:
数组的响应式处理过程
-
在
Observer
类中,对于对象形式,通过调用walk
方法对对象添加了set/get
,而对于数组的则是通过重写数组的方法(这些方法是会使数组的结构发生变化)来进行响应式的实现 -
首先会先判断当前属性中是否有
__proto__
,如果有,则调用protoAugment
方法,否则调用copyAugment
// src\core\observer\index.js
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 初始化实例的vmCount为0
this.vmCount = 0
// 将实例挂载到观察对象的__ob__属性
def(value, '__ob__', this)
// 数组的响应式处理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 为数组中的每一个对象创建一个observer对象
this.observeArray(value)
} else {
// 遍历对象中的每一个属性,转换成setter/getter
this.walk(value)
}
}
- 下面是
protoAugment
方法的实现,该方法就是将src
放在target
的__proto__
上,那么target
是传入的属性值,那接下来具体看一下传入的arrayMethods
是什么
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
- 下面是
arrayMethods
中的代码 - 1)使用数组的原型创建一个新的对象
- 2)将会改变数组方法的方法名放在一个数组中
methodsToPatch
- 3)遍历方法名,执行数组的原始方法
- 4)对插入的新元素,重新遍历数组元素设置为响应式数据
- 5)调用数组的ob对象发送通知
// src\core\observer\array.js
import { def } from '../util/index'
const arrayProto = Array.prototype
// 使用数组的原型创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
// 修改数组元素的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 保存数组原始方法
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 执行数组的原始方法
const result = original.apply(this, args)
// 获取数组对象的ob对象
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 对插入的新元素,重新遍历数组元素设置为响应式数据
if (inserted) ob.observeArray(inserted)
// notify change
// 调用了修改数组的方法,调用数组的ob对象发送通知
ob.dep.notify()
return result
})
})
- 对于没有
__proto__
的会执行copyAugment(value, arrayMethods, arrayKeys)
,其中arrayKeys
是数组原型上的属性名 - 该方法会循环数组上的属性名,并通过
def
重写数组上的方法
// src\core\observer\index.js
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])
}
}
派发更新dep.notify
Watcher
分为三种:Computed Watcher
,用户Watcher
(侦听器),渲染Watcher
- 上面已经说明了对象与数组的响应式处理过程,下面来具体看一下当在
vue
中更改一个数据时,是如何进行派发更新的 - 1)在进行
set
时,会通过dep.notify()
派发更新 - 2)在
notify
中,找到每一个watcher
对象,调用watcher
对象中的update
方法进行更新
// src\core\observer\dep.js
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
- 3)在
update
中,由于lazy
与async
此时为false
,所以执行queueWatcher
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
- 4)在
queueWatcher
中,会通过has
对象保证每个watcher
只添加一次;之后会对flushing
进行判断,如果flushing
为false
,则将当前watcher
放于queue
队列最后,否则根据id
将watcher
插入到队列中;最后最后通过wating
保证对nextTick(flushSchedulerQueue)
的调用逻辑只有一次
export function queueWatcher (watcher: Watcher) {
// 获取watcher的id属性
const id = watcher.id
// 如果当前id为null的话,说明当前watcher还没有被处理,这里是为了防止watcher被重复处理
if (has[id] == null) {
has[id] = true
// flushing 是否刷新
// flushing为true,说明queue队列正在被处理,也就是说明watcher对象正在被处理
// flushing为false,则直接将watcher放在queue队列的最后
// 将当前要处理的watcher放在队列中
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
- 5)在
flushSchedulerQueue
中,首先会将flushing
置为true
,之后会对queue
队列中根据id
进行排列,这里排列的目的有以下三点:- a. 组件被更新顺序是从父组件到子组件,因为先创建父组件,再创建子组件
- b.组件的用户
watcher
要在它对应的渲染watcher
之前运行(因为用户watcher
是在渲染watcher之前创建的) - c. 如果一个组件在父组件的
watcher
执行期间被销毁,那么它对应的watcher
执行都可以被跳过,所以父组件的watcher
应该先执行
- 之后就是对
queue
队列的遍历,可以发现,在遍历的时候,每次都会对queue.length
进行求值,因为在watcher.run()
的时候,很可能用户会再次添加新的watcher
,这样会再次执行到queueWatcher
- 最后通过
resetSchedulerState
把这些控制流程状态的一些变量恢复到初始值,把watcher
队列清空
// src\core\observer\scheduler.js
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) { // 触发beforeUpdate钩子函数
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
- 6) 最后就是执行
watcher
上的run
方法了,先通过this.get()
得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep
模式任何一个条件,则执行watcher
的回调; - 注意回调函数执行的时候会把第一个和第二个参数传入新值
value
和旧值oldValue
,这就是当我们添加自定义watcher
的时候能在回调函数的参数中拿到新旧值的原因。 - 那么对于渲染
watcher
而言,它在执行this.get()
方法求值的时候,会执行getter
方法,也就是updateComponent
来实现组件的更新
// src\core\observer\watcher.js
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
总结
最后以一张图来总结一下上面的原理