vue
生命週期圖示:
html
Vue.js
最顯著的功能就是響應式系統,它是一個典型的MVVM
框架,Model
只是普通的Javascript
對象,修改它則View
會自動更新,這種設計讓狀態管理變得很是簡單而直觀。vue
如何追蹤變化?咱們先看一個簡單的例子:react
<div id="main"> <h1>count:{{times}}</h1> </div> <script src="vue.js"></script> <script> const vm=new Vue({ el:'main', data(){ return { times:1 } }, created(){ let me=this; setInterval(()=>{ me.times++; },1000) } }) </script>
運行後,咱們能夠從頁面中看到,count
後面的times
每隔一秒遞增1
,視圖一直在更新,在代碼中僅僅是經過setInterval
方法每隔一秒來修改vm.times
的值,並無任何dom
操做,那麼vue
是怎麼實現這個過程呢,咱們經過一張圖來看下:express
圖中的Model
就是data
方法返回的{times:1}
,View
是最終在瀏覽器中顯示的DOM
,模型經過Observer,Dep,Watcher,Directive
等一系列對象的關聯,最終和視圖創建起關係。總的來講,vue
在些作了3件事:api
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.js function 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
這個類的源碼:dom
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<any>) { 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.js class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; 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.js let 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.js function 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.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) } } }
addDep
是把dep
添加到Watcher
實例的依賴中,同時又經過dep.addSup(this)
把Watcher
實例添加到dep
的訂閱者中
src/observer/dep.js addSub (sub: Watcher) { this.subs.push(sub) }
至此,指令完成了依賴收集,而且經過Watcher
完成了對數據變化的訂閱。
咱們再看下,當data
發生變化時,視圖是如何自動更新的,在前面的例子中,咱們setInterval
每隔一秒執行一次vm.times++
,數據改變會觸發對象的setter
,執行set
方法體的代碼。
src/core/observer/index.js function 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.js update () { /* 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.js 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) } } } }
run
方法再次對Watcher
求值,從新收集依賴,接下來判斷求值結果和以前value
的關係,若是不變則什麼也不作,若是變了則調用this.cb.call(this.vm,value,oldValue)
方法,這個方法是Directive
實例建立watcher
時傳入的,它對應相關指令的update
方法來真實更新 DOM
,這樣就完成了數據更新到對應視圖的變化過程。Watcher
巧妙的把Observer
和Directive
關聯起來,實現了數據一旦更新,視圖就會自動變化的效果,vue
利用了Object.defineProperty
這個核心技術實現了數據和視圖的綁定。