vue
生命周期图示:
Vue.js
最显著的功能就是响应式系统,它是一个典型的MVVM
框架,Model
只是普通的Javascript
对象,修改它则View
会自动更新,这种设计让状态管理变得非常简单而直观。
如何追踪变化?我们先看一个简单的例子:
count:{ {times}}
运行后,我们可以从页面中看到,count
后面的times
每隔一秒递增1
,视图一直在更新,在代码中仅仅是通过setInterval
方法每隔一秒来修改vm.times
的值,并没有任何dom
操作,那么vue
是怎么实现这个过程呢,我们通过一张图来看下:
图中的Model
就是data
方法返回的{times:1}
,View
是最终在浏览器中显示的DOM
,模型通过Observer,Dep,Watcher,Directive
等一系列对象的关联,最终和视图建立起关系。总的来说,vue
在些做了3件事:
- 通过
Observer
对data
做监听,并提供了订阅某个数据项变化的能力。 - 把
template
编译成一段document fragment
,然后解析其中的Directive
,得到每一个Directive
所依赖的数据项和update
方法。 - 通过
Watcher
把上述2部分结合起来,即把Directive
中的数据依赖通过Watcher
订阅在对应数据的Observer
的Dep
上,当数据变化时,就会触发Observer
的Dep
上的notify
方法通知对应的Watcher
的update
,进而触发Directive
的update
方法来更新dom
视图,最后达到模型和视图关联起来。
Observer
我们来看下vue
是如何给data
对象添加Observer
的,我们知道,vue
的实例创建的过程会有一个生命周期,其中有一个过程就是调用vm.initData
方法处理data
选项,代码如下:
src/core/instance/state.jsfunction initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */)}
我们要注意下proxy
方法,它的功能是遍历data
的key
,把data
上的属性代理到vm
实例上,proxy
源码如下:
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop}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)}
方法主要通过Object.defineProperty
的getter
和setter
方法实现了代理,在 前面的例子中,我们调用vm.times
就相当于访问了vm._data.times
.
在initData
的最后,我们调用了observer(data,this)
来对data
做监听,observer
的源码定义如下:
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 ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob}
它会首先判断value
是否已经添加了_ob_
属性,它是一个Observer
对象的实例,如果是就直接用,否则在value
满足一些条件(数组或对象,可扩展,非vue
组件等)的情况下创建一个Observer
对象,下面看下Observer
这个类的源码:
class Observer { value: any; dep: Dep; vmCount: number; // 将这个对象作为根$data的vm的数量。 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) } } /** *遍历每个属性并将其转换为拥有getter / setter。这个方法应该只在什么时候调用。当obj是对象。 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** 观察数组项的列表。 */ observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}
这个构造函数主要做了这么几件事,首先创建了一个Dep
对象实例,然后把自身this
添加到value
的_ob_
属性上,最后对value
的类型进行判断,如果是数组则观察数组,否则观察单个元素。obsersverArray
方法就是对数组进行遍历,递归调用observer
方法,最终都会调用walk
方法观察单个元素。walk
方法就是对obj
的key
进行遍历。然后调用了defineReactive
,把要观察的data
对象的每个属性都赋予getter
和setter
方法,这样一旦属性被访问或者更新,我们就可以追踪到这些变化。源码如下:
/** * 在对象上定义一个反应性属性 setter,getter。 */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 } // 满足预定义的getter / setter const getter = property && property.get const setter = property && property.set let childOb = !shallow && observe(val) // 在这里添加setter,getter。 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) // 通知data某属性改变,遍历所有的订阅者,就是watcher实例,然后调用watcher实例的update方法 dep.notify() } })}
些方法最核心的部分就是通过调用Object.defineProperty
给data
的每个属性添加getter
和setter
方法。当data
的某个属性被访问时,则会调用getter
方法,判断当Dep.target
不为空时调用dep.depend
和childOb.dep.depend
方法做依赖收集,如果访问的属性是一个数组则会遍历这个数组收集数组元素的依赖,当改变data
的属性时,则会调用setter
方法,这时调用dep.notify
方法进行通知。其中用到的Dep
类是一个简单的观察者模式的实现,然后我们看下源码:
src/core/observer/dep.jsclass Dep { static target: ?Watcher; id: number; subs: Array; constructor () { this.id = uid++ this.subs = [] // 用来存储所有订阅它的watcher } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { // Dep.target表示当前正在计算的watcher,是全局唯一的,同一时间只能有一个watcher被计算 // 把当前Dep的实例添加到当前正在计算的watcher依赖中 Dep.target.addDep(this) } }// 遍历所有的的订阅watcher,调用它们的update方法。 notify () { // 首先稳定用户列表。 const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}
watcher
src/core/observer/watcher.jslet uid = 0/** * 观察者解析表达式,收集依赖项, *当表达式值发生变化时触发回调。 这用于$watch() api和指令。 */export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid 为批处理 this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * 评估getter并重新收集依赖项。 */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // “触摸”每个属性,所以它们都被跟踪。 // 对深度观察的依赖。 if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive.把dep添加到watcher实例的依赖中,同时通过 dep.addsup(this)把watcher实例添加到dep的订阅者中。 */ 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) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** *用户界面。时将调用一个依赖的变化。 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 调用,把watcher实例推入队列中,延迟this.run调用的时机。 queueWatcher(this) } } /** * 调度器的工作界面。会被调度器调用。 * 再次对watcher进行求值,重新收集依赖,接下来判断求值结果和之前value的关系,如果不变,则什么也不做 * 此方法是directive实例创建watcher时传入的,它对应相关指令的update方法来真实更新dom。这样就完成了数据更新到对应视图的变化过程。 * watcher把observer和directive关联起来,实现了数据一旦更新,视图就自动变化的效果。利用object.defineProperty实现了数据和视图的绑定 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入观察和观察对象/阵列甚至应该开火。当值相等时,因为值可以。有突变。 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) } } } } /** * 评估观察者的价值。 这只会被称为懒惰的观察者。 */ evaluate () { this.value = this.get() // 对watcher进行求值,同时收集依赖 this.dirty = false // 不会再对watcher求值,也不会再访问计算属性的getter方法了 } /** * 要看这个观察者收集的所有数据。 */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } }}
Dep
实例在初始化watcher
时,会传入指令的expression
.在前面的例子中expression
是times
.get
方法的功能是对当前watcher
进行求值,收集依赖关系,设置Dep.target
为当前watcher
的实例,this.getter.call(vm,vm)
,这个方法相当于获取vm.times
,这样就触发了对象的getter
.我们之前给data
添加Observer
时,通过上面defineReactive/Object.defineProperty
给data
对象的每一个属性添加getter
和setter
了.
src/core/observer/index.jsfunction defineReactive (obj,key,val,customSetter,shallow) { // 在这里添加setter,getter。 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 } }
当获取vm.times
时,会执行到get
方法体内,由于我们在之前已经设置了Dep.target
为当前Watcher
实例,所以接下来就调用dep.depend()
完成收集,它实际上是执行了Dep.target.addDep(this)
,相当于执行了Watcher
实例的addDep
方法,把Dep
添加到Watcher
实例的依赖中。
src/observer/watcher.jsaddDep (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) } } }
addDep
是把dep
添加到Watcher
实例的依赖中,同时又通过dep.addSup(this)
把Watcher
实例添加到dep
的订阅者中
src/observer/dep.jsaddSub (sub: Watcher) { this.subs.push(sub) }
至此,指令完成了依赖收集,并且通过Watcher
完成了对数据变化的订阅。
我们再看下,当data
发生变化时,视图是如何自动更新的,在前面的例子中,我们setInterval
每隔一秒执行一次vm.times++
,数据改变会触发对象的setter
,执行set
方法体的代码。
src/core/observer/index.jsfunction defineReactive (obj,key,val,customSetter,shallow) { // 在这里添加setter,getter。 Object.defineProperty(obj, key, { enumerable: true, configurable: true, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 通知data某属性改变,遍历所有的订阅者,就是watcher实例,然后调用watcher实例的update方法 dep.notify() } }
src/observer/watcher.jsupdate () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this)// 调用,把watcher实例推入队列中,延迟this.run调用的时机。 } }
src/core/observer/scheduler.js/** * 把一个观察者watcher推入观察者队列。 *将跳过具有重复id的作业,除非它是。 *当队列被刷新时被推。 * 通过nextTick在下一个事件循环周期处理watcher队列,是一种优化手段。因为如果同时观察的数据多次变化,比如同步执行3次vm.time++,同时调用就会触发3次dom操作 * 而推入队列中等待下一个事件循环周期再操作队列里的watcher,因为是同一个watcher,它只会调用一次watcher.run,从而只触发一次dom操作。 */export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // 如果已经刷新,则根据其id将监视器拼接起来。 // 如果已经超过了它的id,它将会立即运行。 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // 队列的冲 if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } }}
function flushSchedulerQueue () { flushing = true let watcher, id // 在刷新前排序队列。 // 这确保: // 1。组件由父元素更新为子元素。(因为父母总是在孩子面前创建) // 2。组件的用户观察者在它的呈现观察者之前运行(因为用户观察者是在渲染观察者之前创建的 // 3。如果组件在父组件的监视程序运行期间被销毁, 它的观察者可以跳过。 queue.sort((a, b) => a.id - b.id) // 不要缓存长度,因为可能会有更多的观察者被推。 // 当我们运行现有的观察者时。遍历queue中watcher的run方法 for (index = 0; index < queue.length; index++) { watcher = queue[index] 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') }}
遍历queue
中Watcher
的run
方法,
src/core/observer/watcher.jsrun () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入观察和观察对象/阵列甚至应该开火。当值相等时,因为值可以。有突变。 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) } } } }
run
方法再次对Watcher
求值,重新收集依赖,接下来判断求值结果和之前value
的关系,如果不变则什么也不做,如果变了则调用this.cb.call(this.vm,value,oldValue)
方法,这个方法是Directive
实例创建watcher
时传入的,它对应相关指令的update
方法来真实更新 DOM
,这样就完成了数据更新到对应视图的变化过程。Watcher
巧妙的把Observer
和Directive
关联起来,实现了数据一旦更新,视图就会自动变化的效果,vue
利用了Object.defineProperty
这个核心技术实现了数据和视图的绑定。