首先讓咱們從最簡單的一個實例Vue
入手:javascript
const app = new Vue({ // options 傳入一個選項obj.這個obj即對於這個vue實例的初始化 })
經過查閱文檔,咱們能夠知道這個options
能夠接受:vue
選項/數據java
datareact
propsgit
propsData(方便測試使用)github
computedexpress
methods數組
watch緩存
選項 / DOM閉包
選項 / 生命週期鉤子
選項 / 資源
選項 / 雜項
具體未展開的內容請自行查閱相關文檔,接下來讓咱們來看看傳入的選項/數據
是如何管理數據之間的相互依賴的。
const app = new Vue({ el: '#app', props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }, data: { msg1: 'Hello world!', arr: { arr1: 1 } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } }, methods: { go () { console.log('This is simple demo') } } })
咱們使用Vue
這個構造函數去實例化了一個vue
實例app
。傳入了props
, data
, watch
, methods
等屬性。在實例化的過程當中,Vue
提供的構造函數就使用咱們傳入的options
去完成數據的依賴管理,初始化的過程只有一次,可是在你本身的程序當中,數據的依賴管理的次數不止一次。
那Vue
的構造函數究竟是怎麼實現的呢?Vue
// 構造函數 function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } // 對Vue這個class進行mixin,即在原型上添加方法 // Vue.prototype.* = function () {} initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
當咱們調用new Vue
的時候,事實上就調用的Vue
原型上的_init
方法.
// 原型上提供_init方法,新建一個vue實例並傳入options參數 Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // 將傳入的這些options選項掛載到vm.$options屬性上 vm.$options = mergeOptions( // components/filter/directive resolveConstructorOptions(vm.constructor), // this._init()傳入的options options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 自身的實例 // 接下來全部的操做都是在這個實例上添加方法 initLifecycle(vm) // lifecycle初始化 initEvents(vm) // events初始化 vm._events, 主要是提供vm實例上的$on/$emit/$off/$off等方法 initRender(vm) // 初始化渲染函數,在vm上綁定$createElement方法 callHook(vm, 'beforeCreate') // 鉤子函數的執行, beforeCreate initInjections(vm) // resolve injections before data/props initState(vm) // Observe data添加對data的監聽, 將data轉化爲getters/setters initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // 鉤子函數的執行, created // vm掛載的根元素 if (vm.$options.el) { vm.$mount(vm.$options.el) } }
其中在this._init()
方法中調用initState(vm)
,完成對vm
這個實例的數據的監聽,也是本文所要展開說的具體內容。
export function initState (vm: Component) { // 首先在vm上初始化一個_watchers數組,緩存這個vm上的全部watcher vm._watchers = [] // 獲取options,包括在new Vue傳入的,同時還包括了Vue所繼承的options const opts = vm.$options // 初始化props屬性 if (opts.props) initProps(vm, opts.props) // 初始化methods屬性 if (opts.methods) initMethods(vm, opts.methods) // 初始化data屬性 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // 初始化computed屬性 if (opts.computed) initComputed(vm, opts.computed) // 初始化watch屬性 if (opts.watch) initWatch(vm, opts.watch) }
咱們在實例化app
的時候,在構造函數裏面傳入的options
中有props
屬性:
props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }
function initProps (vm: Component, propsOptions: Object) { // propsData主要是爲了方便測試使用 const propsData = vm.$options.propsData || {} // 新建vm._props對象,能夠經過app實例去訪問 const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 緩存的prop key const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted observerState.shouldConvert = isRoot for (const key in propsOptions) { // this._init傳入的options中的props屬性 keys.push(key) // 注意這個validateProp方法,不只完成了prop屬性類型驗證的,同時將prop的值都轉化爲了getter/setter,並返回一個observer const value = validateProp(key, propsOptions, propsData, vm) // 將這個key對應的值轉化爲getter/setter 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. // 若是在vm這個實例上沒有key屬性,那麼就經過proxy轉化爲proxyGetter/proxySetter, 並掛載到vm實例上,能夠經過app._props[key]這種形式去訪問 if (!(key in vm)) { proxy(vm, `_props`, key) } } observerState.shouldConvert = true }
接下來看下validateProp(key, propsOptions, propsData, vm)
方法內部到底發生了什麼。
export function validateProp ( key: string, propOptions: Object, // $options.props屬性 propsData: Object, // $options.propsData屬性 vm?: Component ): any { const prop = propOptions[key] // 若是在propsData測試props上沒有緩存的key const absent = !hasOwn(propsData, key) let value = propsData[key] // 處理boolean類型的數據 // handle boolean props if (isType(Boolean, prop.type)) { if (absent && !hasOwn(prop, 'default')) { value = false } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) { value = true } } // check default value if (value === undefined) { // default屬性值,是基本類型仍是function // getPropsDefaultValue見下面第一段代碼 value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldConvert = observerState.shouldConvert observerState.shouldConvert = true // 將value的全部屬性轉化爲getter/setter形式 // 並添加value的依賴 // observe方法的分析見下面第二段代碼 observe(value) observerState.shouldConvert = prevShouldConvert } if (process.env.NODE_ENV !== 'production') { assertProp(prop, key, value, vm, absent) } return value }
// 獲取prop的默認值 function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { // no default, return undefined // 若是沒有default屬性的話,那麼就返回undefined if (!hasOwn(prop, 'default')) { return undefined } const def = prop.default // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined) { return vm._props[key] } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context // 若是是function 則調用def.call(vm) // 不然就返回default屬性對應的值 return typeof def === 'function' && getType(prop.type) !== 'Function' ? def.call(vm) : def }
Vue
提供了一個observe
方法,在其內部實例化了一個Observer
類,並返回Observer
的實例。每個Observer
實例對應記錄了props
中這個的default value
的全部依賴(僅限object
類型),這個Observer
實際上就是一個觀察者,它維護了一個數組this.subs = []
用以收集相關的subs(訂閱者)
(即這個觀察者的依賴)。經過將default value
轉化爲getter/setter
形式,同時添加一個自定義__ob__
屬性,這個屬性就對應Observer
實例。
提及來有點繞,仍是讓咱們看看咱們給的demo
裏傳入的options
配置:
props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }
在往上數的第二段代碼裏面的方法obervse(value)
,即對{key1: 'a', key2: {a: 'b'}}
進行依賴的管理,同時將這個obj
全部的屬性值都轉化爲getter/setter
形式。此外,Vue
還會將props
屬性都代理到vm
實例上,經過vm.key1
,vm.key2
就能夠訪問到這個屬性。
此外,還須要瞭解下在Vue
中管理依賴的一個很是重要的類: Dep
export default class Dep { constructor () { this.id = uid++ this.subs = [] } addSub () {...} // 添加訂閱者(依賴) removeSub () {...} // 刪除訂閱者(依賴) depend () {...} // 檢查當前Dep.target是否存在以及判斷這個watcher已經被添加到了相應的依賴當中,若是沒有則添加訂閱者(依賴),若是已經被添加了那麼就不作處理 notify () {...} // 通知訂閱者(依賴)更新 }
在Vue
的整個生命週期當中,你所定義的響應式的數據上都會綁定一個Dep
實例去管理其依賴。它實際上就是觀察者
和訂閱者
聯繫的一個橋樑。
剛纔談到了對於依賴的管理,它的核心之一就是觀察者Observer
這個類:
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 // dep記錄了和這個value值的相關依賴 this.dep = new Dep() this.vmCount = 0 // value其實就是vm._data, 即在vm._data上添加__ob__屬性 def(value, '__ob__', this) // 若是是數組 if (Array.isArray(value)) { // 首先判斷是否能使用__proto__屬性 const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) // 遍歷數組,並將obj類型的屬性改成getter/setter實現 this.observeArray(value) } else { // 遍歷obj上的屬性,將每一個屬性改成getter/setter實現 this.walk(value) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ // 將每一個property對應的屬性都轉化爲getter/setters,只能是當這個value的類型爲Object時 walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * Observe a list of Array items. */ // 監聽array中的item observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
walk
方法裏面調用defineReactive
方法:經過遍歷這個object
的key
,並將對應的value
轉化爲getter/setter
形式,經過閉包維護一個dep
,在getter
方法當中定義了這個key
是如何進行依賴的收集,在setter
方法中定義了當這個key
對應的值改變後,如何完成相關依賴數據的更新。可是從源碼當中,咱們卻發現當getter
函數被調用的時候並不是就必定會完成依賴的收集,其中還有一層判斷,就是Dep.target
是否存在。
/** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) { // 每一個屬性新建一個dep實例,管理這個屬性的依賴 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 // 遞歸去將val轉化爲getter/setter // childOb將子屬性也轉化爲Observer let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 定義getter -->> reactiveGetter get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 定義相應的依賴 if (Dep.target) { // Dep.target.addDep(this) // 即添加watch函數 // dep.depend()及調用了dep.addSub()只不過中間須要判斷是否這個id的dep已經被包含在內了 dep.depend() // childOb也添加依賴 if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value }, // 定義setter -->> reactiveSetter set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 對獲得的新值進行observe childOb = observe(newVal) // 相應的依賴進行更新 dep.notify() } }) }
在上文中提到了Dep
類是連接觀察者
和訂閱者
的橋樑。同時在Dep
的實現當中還有一個很是重要的屬性就是Dep.target
,它事實就上就是一個訂閱者,只有當Dep.target
(訂閱者)存在的時候,調用屬性的getter
函數的時候才能完成依賴的收集工做。
Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
那麼Vue
是如何來實現訂閱者
的呢?Vue
裏面定義了一個類: Watcher
,在Vue
的整個生命週期當中,會有4類地方會實例化Watcher
:
Vue
實例化的過程當中有watch
選項
Vue
實例化的過程當中有computed
計算屬性選項
Vue
原型上有掛載$watch
方法: Vue.prototype.$watch,能夠直接經過實例調用this.$watch
方法
Vue
生成了render
函數,更新視圖時
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { // 緩存這個實例vm this.vm = vm // vm實例中的_watchers中添加這個watcher 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 for batching this.active = true this.dirty = this.lazy // for lazy watchers .... // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } // 經過get方法去獲取最新的值 // 若是lazy爲true, 初始化的時候爲undefined this.value = this.lazy ? undefined : this.get() } get () {...} addDep () {...} update () {...} run () {...} evaluate () {...} run () {...}
Watcher
接收的參數當中expOrFn
定義了用以獲取watcher
的getter
函數。expOrFn
能夠有2種類型:string
或function
.若爲string
類型,首先會經過parsePath
方法去對string
進行分割(僅支持.
號形式的對象訪問)。在除了computed
選項外,其餘幾種實例化watcher
的方式都是在實例化過程當中完成求值及依賴的收集工做:this.value = this.lazy ? undefined : this.get()
.在Watcher
的get
方法中:
!!!前方高能
get () { // pushTarget即設置當前的須要被執行的watcher pushTarget(this) let value const vm = this.vm if (this.user) { try { // $watch(function () {}) // 調用this.getter的時候,觸發了屬性的getter函數 // 在getter中進行了依賴的管理 value = this.getter.call(vm, vm) console.log(value) } catch (e) { handleError(e, vm, `getter for watcher "${this.expression}"`) } } else { // 若是是新建模板函數,則會動態計算模板與data中綁定的變量,這個時候就調用了getter函數,那麼就完成了dep的收集 // 調用getter函數,則同時會調用函數內部的getter的函數,進行dep收集工做 value = this.getter.call(vm, vm) } // "touch" every property so they are all tracked as // dependencies for deep watching // 讓每一個屬性都被做爲dependencies而tracked, 這樣是爲了deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value }
一進入get
方法,首先進行pushTarget(this)
的操做,此時Vue
當中Dep.target = 當前這個watcher
,接下來進行value = this.getter.call(vm, vm)
操做,在這個操做中就完成了依賴的收集工做。仍是拿文章一開始的demo
來講,在vue
實例化的時候傳入了watch
選項:
props: { a: { type: Object, default () { return { key1: 'a', key2: { a: 'b' } } } } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } },
在Vue
的initState()
開始執行後,首先會初始化props
的屬性爲getter/setter
函數,而後在進行initWatch
初始化的時候,這個時候初始化watcher
實例,並調用get()
方法,設置Dep.target = 當前這個watcher實例
,進而到value = this.getter.call(vm, vm)
的操做。在調用this.getter.call(vm, vm)
的方法中,便會訪問props
選項中的a
屬性即其getter
函數。在a
屬性的getter
函數執行過程當中,由於Dep.target
已經存在,那麼就進入了依賴收集
的過程:
if (Dep.target) { // Dep.target.addDep(this) // 即添加watch函數 // dep.depend()及調用了dep.addSub()只不過中間須要判斷是否這個id的dep已經被包含在內了 dep.depend() // childOb也添加依賴 if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } }
dep
是一開始初始化的過程當中,這個屬性上的dep
屬性。調用dep.depend()
函數:
depend () { if (Dep.target) { // Dep.target爲一個watcher Dep.target.addDep(this) } }
Dep.target
也就剛纔的那個watcher
實例,這裏也就至關於調用了watcher
實例的addDep
方法: watcher.addDep(this)
,並將dep
觀察者傳入。在addDep
方法中完成依賴收集:
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) } } }
這個時候依賴完成了收集,當你去修改a
屬性的值時,會調用a
屬性的setter
函數,裏面會執行dep.notify()
,它會遍歷全部的訂閱者,而後調用訂閱者上的update
函數。
initData
過程和initProps
相似,具體可參見源碼。
以上就是在initProps
過程當中Vue
是如何進行依賴收集的,initData
的過程和initProps
相似,下來再來看看initComputed
的過程.
在computed
屬性初始化的過程中,會爲每一個屬性實例化一個watcher
:
const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { // 新建_computedWatchers屬性 const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] // 若是computed爲funtion,即取這個function爲getter函數 // 若是computed爲非function.則能夠單獨爲這個屬性定義getter/setter屬性 let getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. // lazy屬性爲true // 注意這個地方傳入的getter參數 // 實例化的過程中不去完成依賴的收集工做 watchers[key] = new Watcher(vm, getter, 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) } } }
可是這個watcher
在實例化的過程當中,因爲傳入了{lazy: true}
的配置選項,那麼一開始是不會進行求值與依賴收集的: this.value = this.lazy ? undefined : this.get()
.在initComputed
的過程當中,Vue
會將computed
屬性定義到vm
實例上,同時將這個屬性定義爲getter/setter
。當你訪問computed
屬性的時候調用getter
函數:
function 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
存在的狀況下,首先判斷watcher.dirty
屬性,這個屬性主要是用於判斷這個computed
屬性是否須要從新求值,由於在上一輪的依賴收集的過程中,觀察者已經將這個watcher
添加到依賴數組當中了,若是觀察者發生了變化,就會dep.notify()
,通知全部的watcher
,而對於computed
的watcher
接收到變化的請求後,會將watcher.dirty = true
即代表觀察者發生了變化,當再次調用computed
屬性的getter
函數的時候便會從新計算,不然仍是使用以前緩存的值。
initWatch
的過程當中其實就是實例化new Watcher
完成觀察者的依賴收集的過程,在內部的實現當中是調用了原型上的Vue.prototype.$watch
方法。這個方法也適用於vm
實例,即在vm
實例內部調用this.$watch
方法去實例化watcher
,完成依賴的收集,同時監聽expOrFn
的變化。
總結:
以上就是在Vue
實例初始化的過程當中實現依賴管理的分析。大體的總結下就是:
initState
的過程當中,將props
,computed
,data
等屬性經過Object.defineProperty
來改造其getter/setter
屬性,併爲每個響應式屬性實例化一個observer
觀察者。這個observer
內部dep
記錄了這個響應式屬性的全部依賴。
當響應式屬性調用setter
函數時,經過dep.notify()
方法去遍歷全部的依賴,調用watcher.update()
去完成數據的動態響應。
這篇文章主要從初始化的數據層面上分析了Vue
是如何管理依賴來到達數據的動態響應。下一篇文章來分析下Vue
中模板中的指令和響應式數據是如何關聯來實現由數據驅動視圖,以及數據是如何響應視圖變化的。