家喻户晓,Vue是以数据驱动视图展现的,即Vue会监听数据的变动,从而主动从新渲染页面的构造。
Vue次要通过三步走来实现这个性能:
第一步是对数据进行响应式革新,即对数据的读写操作进行劫持;
第二步是对模板依赖的数据进行收集;
第三步是在数据发生变化时,触发组件更新。
数据响应式革新
0. defineReactive
对数据进行响应式革新的外围代码
// core/observer/index.jsexport 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.target`不为空时,通常指向一个`watcher`实例 dep.depend() // 属性被收集到以后`watcher`实例的依赖数组中 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() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })}
通过Object.defineProperty
批改对象属性的属性描述符descriptor,来实现劫持对象属性的读写操作。
前置常识,对象的属性分为data型和accessor型。
data型的属性描述符蕴含value和writable;accessor型的属性描述符蕴含getter和setter函数(两者至多存在一个)。
由上述代码能够看出,所有属性被解决成了accessor型属性,即通过getter和setter来实现读写,比方当咱们读取person
对象上的属性name
,理论失去的是name
的属性拜访符中的getter函数执行后的返回值。
上述的响应式革新中,每个属性会对应一个dep实例:const dep = new Dep()
,如果属性值val是对象或数组,会被列入察看对象,他的属性会被递归进行响应式革新let childOb = !shallow && observe(val)
。
get函数被用于收集依赖:
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 },
该函数在对象属性被拜访时会执行,如果Dep.target
不为空,即当下有一个监听器watcher
在收集依赖,就进行依赖的收集,dep实例会被收集到该watcher
的依赖数组newDeps中,同时dep也会将此watcher
记录到本人的subs订阅数组中,记录有谁订阅了本人的变更。
如果childOb
不为空(即属性值val为数组或对象,且可扩大),就对val的__ob__
属性也进行收集操作。
如果value是数组,对数组中的对象元素也进行依赖收集。
就是一层层的递归收集。
set函数被用于告诉变更:
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() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }
如果属性的新值是属性或对象,就更新childOb
。
实现属性赋值操作后,调用dep.notify()
,告诉所有订阅了本人的watcher
实例执行update
操作,即上面代码中的for循环操作。
// core/observer/dep.jsexport default class Dep { // ... notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}
如果是同步this.sync
的watcher会立刻被执行,否则会插入到watcher队列queueWatcher(this)
排队期待执行:
// core/observer/watcher.jsupdate () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } }
1. initInjections
// core/instance/inject.jsexport function initInjections (vm: Component) { const result = resolveInject(vm.$options.inject, vm) if (result) { toggleObserving(false) Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) toggleObserving(true) }}
通过执行resolveInject
解析inject中的数据,解析后果赋值给result。result蕴含inject中所有的key,如果下级组件中没有对应inject数据的provide,就赋默认值,简略来说大抵就是result[key] = inject[key] || default
。
再调用defineReactive(vm, key, result[key])
将这些key加到vm
实例上,即inject中的数据也会进行响应式解决。
假如存在一个inject:["person"]
,如果person
的值是个对象,它的值会被列为察看对象,以后子组件的watcher
会对该对象的属性依赖收集,在下级组件中更改了原始person的某个属性,就会触发子组件的更新。
2. initProps
// core/instance/state.jsfunction initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } defineReactive(props, key, value, () => { if (!isRoot && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } }) } else { defineReactive(props, key, value) } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true)}
propsOptions
,接管的是vm.$options.props
,是申明接管的props的配置,;vm.$options.propsData
是理论接管到的props数据。
调用defineReactive(props, key, result[key])
将propsOptions上的key加到props
对象上,即vm._props
上,进行响应式解决,如果是在vm
上不存在的key,通过proxy(vm, '_props', key)
操作,使得能够通过vm
间接拜访到_props
的属性,而不须要通过_props
对象来拜访。
3. initData
// 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 */)}
data选项会被挂在vm._data
上,从上述代码中能够看出,data必须是一个对象,或者返回值为对象的函数。
通过proxy(vm, '_data', key)
操作,vm能够间接拜访到_data
的属性,而不须要通过_data
对象来拜访。
最初通过observe(data, true /* asRootData */)
来对数据做响应式革新,能够看到这个observe办法多传了一个参数值为true
,标记以后解决的数据是$options.data
对象。
observe
办法理论是创立一个新的ob实例,数据的__ob__
属性置信在控制台打印过vue中数据的同学都不生疏,都是指向ob实例。
// core/observe/index.jsexport 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}
// core/observe/index.jsexport 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 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } } /** * Walk through all properties 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]) } }}
从下面Observer
的构造函数中能够看出,创立ob
实例后,这个实例就挂载数据的__ob__
属性上了,因为在iniDats
时传递给构造函数的参数是个对象,所以会调用walk办法,持续看walk办法的定义,能够看出,是把这个对象的属性一一取出,调用defineReactive(obj, keys[i])
进行响应式革新。
4. initComputed
// core/instance/state.jsfunction initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } else if (vm.$options.methods && key in vm.$options.methods) { warn(`The computed property "${key}" is already defined as a method.`, vm) } } }}
取得每个计算属性对应watcher的初始值
从上述代码中能够看出,会遍历computed所有的属性,每个属性对应配置一个watcher实例,watcher实例在创立时,会调用每个computed对应的getter获取一遍初始值,放在watcher实例的value
属性上
// core/observer/watcher.jsexport default class Watcher { // ... constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // ... this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ 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 { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } // ...}
// core/observer/dep.jsexport function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target}
能够看到,执行watcher实例的get()
办法时,会进行一个pushTarget(this)
的操作,此操作批改了Dep.target
,使它指向了以后的watcher实例,如果某个computed属性依赖了data中的某个属性,须要读取data中的某个属性值,就会触发该data属性的getter函数,使得该data属性被收集到以后watcher实例的依赖数组中。
实现computed属性的取值后,执行popTarget()
,即上面的代码:
// core/observer/dep.jsexport function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1]}
会使Dep.target
指回上一个watcher实例。
最初清理依赖this.cleanupDeps()
,将不再关联的依赖dep其订阅数组中对应的watcher移除,将newDeps
赋值给deps
并清空newDeps
,代表该watcher
实例一次依赖收集结束。
计算属性被读取时,其对应watcher依赖的数据会被以后watcher收集为本身的依赖
如果computed某个属性的标识符不在vm
实例上,就继续执行defineComputed(vm, key, userDef)
,会将给vm实例增加一个名为key
的属性,该属性的getter函数由下述代码定义:
// core/instance/state.jsfunction createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } }}
即在这个计算属性被读取时,会拿到它所对应的watcher实例,如果以后Dep.target
不为null时,watcher会执行实例办法depend()
。
export default class Watcher { // ... /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } // ...}
能够看到此watcher实例的依赖deps会被一一取出,执行dep实例的depend办法:
// core/observerdep.jsdepend () { if (Dep.target) { Dep.target.addDep(this) } }
即此watcher实例的依赖都会被收集到以后Dep.target
指向的watcher实例的依赖数组中。
5. initWatch
// core/instance/state.jsfunction initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } }}function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options)}Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { const info = `callback for immediate watcher "${watcher.expression}"` pushTarget() invokeWithErrorHandling(cb, vm, [watcher.value], vm, info) popTarget() } return function unwatchFn () { watcher.teardown() } }
initWatch的内容比较简单,就是通过调用createWatcher(vm, key, handler)
,一一对应生成watcher
实例,并且给watcher
实例标记options.user = true
,代表这个watcher
是用户配置的。
每个watch通常是有一个对应的表达式(通常是vm的data数据)和一个对应的回调函数,应用场景通常是当vm中的某些数据产生扭转时,用户须要做一些自定义的操作来做解决。
与computed中生成对应watcher实例相似,watcher实例在创立时,每个watch对应的表达式就会被求值一遍,即vm实例上的某些数据属性被读取,这些属性对应的dep会被收集到该watcher
实例的依赖数组中,求得的值会放在watcher
实例的value
属性上,如果某个watch配置了immediate
,就立刻执行一遍watch对应的回调函数,入参为watcher
的value
属性值。
能够看到在执行回调函数前,执行了一个pushTarget()
,此时Dep.target会指向空,所以在回调函数执行过程中,如果vm的某些数据属性被拜访,这些属性不会被收集依赖,因为属性的getter
函数中在属性被收集依赖前有个对Dep.target
的判空查看。
6. 一些阐明
__ob__
是给对象加的属性,指向observer实例,ob和对象是一对一,代表这个对象被察看,该对象的属性的读写操作会被做响应式解决,即被劫持。
dep
是给属性配置的用于依赖收集的,通常对象的某个属性与dep是一对一,能够被多个watcher收集,即多个watcher实例在监听这个属性的变动;__ob__
是对象的非凡属性,它也有本人的dep,能够被watcher
收集。
一个watcher实例只会关联一个vm,一个vm实例能够关联多个watcher
,watcher实例会放在vm._watchers
数组中;渲染watcher
还会放在vm._watcher
上,渲染watcher
从字面上了解就是与组件渲染无关的watcher
。