目标
- 形成一个闭环认知,画流程图
- 总结
响应式原理流程
vue2响应式原理主要涉及使用Object.defineProperty重写数据的setter和getter使其变为响应式数据
initState
路径:src\core\instance\state.ts
在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法,它的定义在 src/core/instance/state.js 中。
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
这里可以看出initState是按顺序对props、methods、data、computed以及watcher等属性做了初始化操作
这里着重介绍一下props以及data
initProps
路径:src\core\instance\state.ts
做了两件事:
- 调用defineReactive将每个prop的值变成响应式,可以通过vm._props.xxx访问定义在props中对应的属性
- 通过proxy把vm._props.xxx访问代理到vm.xxx上
initData
路径:src\core\instance\state.ts
做了两件事:
- 调用oberve方法观测整个data的变化,把data也变成响应式,可以通过vm._data.xxx访问定义在data中对应的属性
- 通过proxy把vm._data.xxx访问代理到vm.xxx上
proxy
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
proxy就是通过Object.defineProperty将对target[sourceKey][key]的读写变成了对target[key]的读写
observe
observe就是用来检测数据的变化,它的定义在 src/core/observer/index.js 中:
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe将传进来的值进行判断,如果是vnode或者不是对象就不往下进行;如果往下进行,检测value对象是否有__ob__属性,如果没有,则实例化一个observer对象作为该对象的__ob__属性,将__ob__对象里的vmCount属性值加1,最后返回这个__ob__对象
Observer
路径: src/core/observer/index.js
上面observe有个步骤就是实例化Observer对象作为value对象的__ob__属性,而这个observer实际上的行为可以概括为给对象的属性添加getter和setter,并且通过getter和setter来监听属性的变化
/**
* 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.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}/**
* 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.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Observer是一个类,他的构造函数做了这些事情:
- 为自己的属性dep实例化一个Dep对象,设置vmCount为0
- 并且将自己这个Observer实例作为传进来的value对象的
__ob__属性(通过def函数,其实也是对Object.defineProperty的封装) - 然后如果传进来的值是数组,则对这个数组执行observeArray方法,否则,也就是对象,执行walk方法
- observeArray是遍历数组且再次对用observe方法(这也就是为什么通过数组下标改变数组的值不会进行响应式)
- walk方法是遍历对象的key,对每个key调用
defineReactive
defineReactive
defineReactive就是通过Object.Property给对象的属性添加getter和setter
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* 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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
在getter和setter中都会判断属性是否原本就有getter和setter,如果有就调用原本的getter和setter,保证不会覆盖之前已经定义的getter/setter
在getter中会进行依赖收集
在setter中会获取新的值,然后重新observe这个新的值,确保响应式,然后通知观察者更新
依赖收集
Dep
路径: src/core/observer/dep.js
dep实际上就是对Watcher的一种管理
dep.depend()触发Dep.target.addDep,Dep.target就是一个Watcher,addDep方法会将dep实例化对象及其id存到newDeps以及newDepIds两个数组中,并调用dep的addSub方法,将该Watcher放入这个数据的dep持有的subs中
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)
}
}
}
Watcher
路径: src/core/observer/watcher.js
在Vue挂载的过程中,会调用mountComponent方法,这个方法的核心就是实例化一个渲染Watcher,它的回调函数中会调用updateComponent方法,在此方法调用vm._render方法先生成虚拟Node,最终调用vm._update更新dom。
Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
而在渲染VNode过程中,就会访问vm上面的数据,这个时候就触发了数据的getter进行数据依赖收集也就是dep.depend()
PS: 以上watcher和dep是概括记录一下今晚所学内容,后续还需详细展开(比如实例化watcher的过程怎么调用updateComponent方法、vm._update以及vm._render等等)
上面讲的都是依赖收集的环节,接下来讲讲派发更新的环节
派发更新
当数据被赋予新的值,会触发数据的setter,setter做了两个关键的事情,第一步是setter对新的值进行observe变成响应式的值;然后第二步进行dep.notify,通知subs里存放的订阅者(Watcher)进行更新。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// ...
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* 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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对新的值进行observe使新的值也变为响应式对象
childOb = !shallow && observe(newVal)
// 通知订阅者队列里的watcher进行视图更新
dep.notify()
}
})
}
通知subs订阅者队列中的watcher进行更新的notify函数,实际就是遍历subs队列,让每一个watcher调用update方法
class Dep {
// ...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
class Watcher {
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
这里对于watcher的不同状态会执行不同的逻辑,在一般的组件更新数据的场景,会走到最后一个queueWatcher(this)的逻辑,queueWatcher是用了一个队列 来保存要进行视图更新的watcher,这个队列也是vue进行派发更新的优点,vue不会在每次数据改变后就触发watcher的回调进行视图更新,而是将这些watcher放入queue这个队列中,调用nexttick以异步的方式调用flushSchedulerQueue
export function queueWatcher(watcher: Watcher) {
const id = watcher.id
if (has[id] != null) {
return
}
if (watcher === Dep.target && watcher.noRecurse) {
return
}
has[id] = true
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 (__DEV__ && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
那么上面说到flushSchedulerQueue到底是干嘛的呢?接下来我们来看看他的源码,位置在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.
// 1. 对队列做了由小到大的排序,因为父组建的创建过程是先于子的,所以watcher的创建也是先父后子
// 2. 用户自定义的watcher要优先于渲染watcher的执行;因为用户自定义watcher是在渲染watcher前创建的
// 3. 如果一个组件在其父组件的watcher执行期间被销毁,那么他对应的watcher都可以跳过,所以父组件的watcher应该先执行
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) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
....
}
在flushSchedulerQueue里面,先是对queue队列里的watcher进行排队,按id来从小到大排序,也就是说,父组件的watcher优先执行,然后遍历queue里的每个watcher执行他们的run方法,这个方法就是用于更新视图的,当然我们可以看到这个遍历的长度是queue.length,这个队列长度是会变的,在执行watcher.run()使,用户可能会添加新的watcher,这时就会执行到queueWatcher的else逻辑中:
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
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)
}
// ...
}
}
这时flushing为true,是将待插入的watcher插入到id从小到大排序的queue队列中。
我们来看看watcher.run这个直接导致视图更新的方法吧,位置在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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
这里是通过自身的get方法获取当前的值,然后做判断,如果新值不等于旧值,新值是对象或者deep模式这个三个条件满足一个就执行watcher的回调,回调函数里传入了新值和旧值,所以这就是我们自定义的watcher能拿到新值和旧值的原因,而渲染的watcher在调用get方法获取当前值时就会执行getter方法:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
这就是我们修改数据后会引起视图的更新组件的重新渲染,也就是所谓的响应式,接着就会执行patch的过程,这就涉及到虚拟dom和diff算法
画一个流程图
总结
vue2的数据响应式原理和observer、watcher、dep这个三个类有关,在vue初始化data的时候,会做两件事:
- 将this.data.xxx代理到this.xxx
- 通过observe方法将data以及data里的数据变为响应式对象
observe会判断传进来的数据是否为对象,如果是对象会给数据附上一个__ob__的属性,这个属性就是一个observer实例化对象,observer会通过数据劫持将数据对象变为响应式对象,数据劫持会重写数据的getter和setter方法,触发数据的getter方法会收集依赖,触发setter方法会通知订阅者更新视图,那这个收集依赖实际上就是调用dep的depend方法,depend的方法会将当前的watcher也就是Dep.target存入自己的一个订阅者队列subs,当触发setter方法时调用dep.notify,这个方法会遍历订阅者队列subs里的watcher,让每个watcher调用update方法来进行更新视图。
update方法会将这些要进行派发更新的watcher放入一个队列中(queue),然后使用nexttick执行flushSchedulerQueue,也就是进行异步更新视图,在flushSchedulerQueue里会按id从小到大排序watcher(也就是父组件的watcher先执行),然后遍历queue里的watcher,依次执行自身的run方法,在run方法中就会调用get方法,也就会触发getter方法,也就是updateComponent方法,从而导致视图更新。