(距離上一次寫文章已通過去了四個月,羞愧...)這幾個月對vue的使用很多,可是自覺地始終停留在比較粗淺的層面,一直沒法提高,因此嘗試着開始閱讀源碼。 文中內容僅表明我的理解,若是錯誤,歡迎指正。html
Vue中一個顯著特性是數據響應式系統:當數據被修改時,視圖會相應更新。從而方便的完成狀態管理。官方文檔中對此進行了簡要的描述,本文將結合vuejs的源碼,作出進一步的解析。vue
首先簡單介紹一些在響應式系統中重要的概念。react
vue實例中的數據項git
數據屬性的觀察者,監控對象的讀寫操做。github
(dependence的縮寫),字面意思是「依賴」,扮演角色是消息訂閱器,擁有收集訂閱者、發佈更新的功能。express
消息訂閱者,能夠訂閱dep,以後接受dep發佈的更新並執行對應視圖或者表達式的更新。npm
dep
和watcher
的關係,能夠理解爲:dep
是報紙,watcher
是訂閱了報紙的人,若是他們創建了訂閱 的關係,那麼每當報紙有更新的時候,就會通知對應的訂閱者們。api
暫且認爲就是在瀏覽器中顯示的dom(關於virtual dom的內容暫時不在本文討論)數組
watcher在自身屬性中添加dep的行爲,後面會詳細介紹瀏覽器
dep在自身屬性中添加watcher的行爲,後面會詳細介紹
首先給出官方文檔的流程圖
在此基礎上,咱們根據源碼更細一步劃分出watcher和data之間的部分,即Dep
和observer
。
總的來講,vue的數據響應式實現主要分紅2個部分:
第一部分是上圖中data
、observer
、dep
之間聯繫的創建過程,第二部分是watcher
、dep
的關係創建
本文中採用的源碼是vuejs 2.5.0,Git地址
PS:簡單的代碼直接添加中文註釋,因此在關鍵代碼部分作<數字>
標記,在後文詳細介紹
首先咱們在源碼中找到vue進行數據處理的方法initData
:
/* 源碼目錄 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)) { //<1>data屬性代理 proxy(vm, `_data`, key) } } // observe data //對data調用observe observe(data, true /* asRootData */) }
這一段代碼主要作2件事:
代碼<1>
在while
循環內調用proxy
函數把data的屬性代理到vue實例上。完成以後能夠經過vm.key
直接訪問data.key
。以後對data
調用了observe
方法,在這裏說明一下,若是是在實例化以前添加的數據,由於被observe
過,因此會變成響應式數據,而在實例化以後使用vm.newKey = newVal
這樣設置新屬性,是不會自動響應的。解決方法是:
- 若是你知道你會在晚些時候須要一個屬性,可是一開始它爲空或不存在,那麼你僅須要設置一些初始值 - 使用`vm.$data`等一些api進行數據操做
接下來來看對應代碼:
/* 源碼目錄 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 //檢測當前數據是否被observe過,若是是則沒必要重複綁定 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( //<1>檢測當前的數據是不是對象或者數組,若是是,則生成對應的Observer observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
代碼<1>
處,對傳入的數據對象進行了判斷,只對對象和數組類型生成Observer
實例,而後看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實例 關於dep的結構稍後詳細介紹 this.dep = new Dep() this.vmCount = 0 //def函數給當前數據添加不可枚舉的__ob__屬性,表示該數據已經被observe過 def(value, '__ob__', this) //<1>對數組類型的數據 調用observeArray方法;對對象類型的數據,調用walk方法 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. */ /* 循環遍歷數據對象的每一個屬性,調用defineReactive方法 只對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. */ /* observe數組類型數據的每一個值, */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } /* defineReactive的核心思想改寫數據的getter和setter */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { //<2>生成一個dep實例,注意此處的dep和前文Observer類裏直接添加的dep的區別 const dep = new Dep() //檢驗該屬性是否容許從新定義setter和getter const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters // 獲取原有的 getter/setters const getter = property && property.get const setter = property && property.set //<3>此處對val進行了observe let childOb = !shallow && observe(val) //<4>下面的代碼利用Object.defineProperty函數把數據轉化成getter和setter,而且在getter和setter時,進行了一些操做 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // dep.depend()其實就是dep和watcher進行了互相綁定,而Dep.target表示須要綁定的那個watcher,任什麼時候刻都最多隻有一個,後面還會解釋 dep.depend() if (childOb) { //<5>當前對象的子對象的依賴也要被收集 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 } //<6>觀察新的val並通知訂閱者們屬性有更新 childOb = !shallow && observe(newVal) dep.notify() } }) }
_![圖片描述][2]ob_
屬性上,而後把_ob_
掛在該數據上,它是該數據項被observe
的標誌,咱們能夠在控制檯看到這個屬性,,例如://例子 1 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>vue demo</title> </head> <body> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <div id="app"> <div>obj:{{ obj}}</div> </div> </body> <script> app = new Vue({ el: '#app', data: { str: "a", obj: { key: "val" } } }); console.log(app._data) </script> </html>
在控制檯咱們能夠看到這樣的數據
能夠看到,首先,data對象上已經有_ob_
屬性,這是被observe
的標誌;其次,obj
和arr
屬性上有_ob_
屬性,而str
沒有,這個進一步證實了前文提到的:observe
只對對象和數組有效
observe
裏面的每一個值,對於對象,咱們執行walk()
方法,而walk()
就是對於當前數據對象的每一個key,執行defineReactive()
方法,因此接下來重點來看defineReactive()
。defineReactive()
中,在代碼<2>
處生成了一個dep
實例,並在接下來的代碼裏,把這個dep
對象放在當前數據對象的key
(好比上面例子1中的str
)的getter
裏,這個以前Observer
中的dep
是有區別的:
Observer
中的dep
掛在Object
或者Array
類型的數據的dep
屬性上,能夠在控制檯直接查看;dep
掛在屬性的getter/setter上
,存在於函數閉包中,不可直接查看爲何會有2種`Dep`呢?由於對於`Object`或者`Array`類型的數據,可能會有**添加
或者刪除成員的操做而其餘類型的值只有賦值操做,賦值操做能夠在getter/setter上
中檢測到。**,
代碼<3>
處的是爲了處理嵌套的數據對象,好比例子1中,data
是最頂層的Object
,obj
就是data
下的Object
,而obj
裏面也能夠再繼續嵌套數據,有了此處的代碼以後,就能夠對嵌套的每一層都作observe
處理。代碼<4>
處是defineReactive()
的核心:利用Object.defineProperty()
(這個函數建議瞭解一下mdn地址)在當前屬性的getter和setter中插入操做:
watcher
(也就是Dap.target
)和dep
之間的綁定,這裏有個注意點是在代碼<5>
處,若是當前數據對象存在子對象,那麼子對象的dep
也要和當前watcher
進行綁定,以此類推。val
,而後經過dep.notify()
來通知當前dep所綁定的訂閱者們數據有更新。接下來介紹一下dep
。源碼以下:
/* 源碼目錄 src/core/observer/dep.js */ let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. */ export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } //添加一個watcher addSub (sub: Watcher) { this.subs.push(sub) } //移除一個watcher removeSub (sub: Watcher) { remove(this.subs, sub) } //讓當前watcher收集依賴 同時Dep.target.addDep也會觸發當前dep收集watcher depend () { if (Dep.target) { Dep.target.addDep(this) } } //通知watcher們對應的數據有更新 notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
這個類相對簡單不少,只有2個屬性:第一個是id
,在每一個vue實例中都從0開始計數;另外一個是subs
數組,用於存放wacther
,根絕前文咱們知道,一個數據對應一個Dep
,因此subs
裏存放的也就是依賴該數據須要綁定的wacther
。
這裏有個Dep.target
屬性是全局共享的,表示當前在收集依賴的那個Watcher,在每一個時刻最多隻會有一個。
接下里看watcher的源碼,比較長,可是咱們只關注其中的幾個屬性和方法:
/* 源碼目錄 src/core/observer/watcher.js */ /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ /* watcher用來解析表達式,收集依賴,而且當表達式的值改變時觸發回調函數 用在$watch() api 和指令中 */ export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: ISet; newDepIds: ISet; getter: Function; value: any; 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 for batching this.active = true this.dirty = this.lazy // for lazy watchers //deps和newDeps表示現有的依賴和新一輪收集的依賴 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 //<1>解析getter的表達式 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { //<2>獲取實際對象的值 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.lazy爲true是計算屬性的watcher,另外處理,其餘狀況調用get 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() //<3>清除先前的依賴 this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ /* 給當前指令添加依賴 */ 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 } /** * Subscriber interface. * Will be called when a dependency changes. */ /* 訂閱者的接口 當依賴改變時會觸發 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ /* 調度接口 調度時會觸發 */ run () { if (this.active) { //<14>從新收集依賴 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) { 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 the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ 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 } }
首先看官方文檔的英文註釋可知,watcher用於watcher用來解析表達式,收集依賴,而且當表達式的值改變時觸發回調函數,用在$watch()
api 和指令之中。
watcher函數主要內容是:
deps
、newDeps
、depIds
、newDepIds
,分別表示現有依賴和新一輪收集的依賴,這裏的依賴就是前文介紹的數據對應的dep
。<1>
判斷傳入的表達式類型:多是函數,也多是表達式。若是是函數,那麼直接設置成getter,若是是表達式,因爲代碼<2>
處的expOrFn
只是字符串,好比例子1中的obj.key
,在這裏僅僅是一個字符串,因此要用parsePath
獲取到實際的值代碼<3>
`cleanupDeps`清除舊的依賴。這是必需要作的,由於數據更新以後可能有新的數據屬性添加進來,前一輪的依賴中沒有包含這個新數據,因此要從新收集。get()
首先從新收集依賴,而後使用this.cb.call
更新模板或者表達式的值。在最後,咱們再總結一下這個流程:首先數據從初始化data開始,使用observe
監控數據:給每一個數據屬性添加dep
,而且在它的getter過程添加收集依賴操做,在setter過程添加通知更新的操做;在解析指令或者給vue實例設置watch
選項或者調用$watch
時,生成對應的watcher
並收集依賴。以後,若是數據觸發更新,會通知watcher
,wacther
在從新收集依賴以後,觸發模板視圖更新。這就完成了數據響應式的流程。
本文的流程圖根據源碼的過程畫出,而在官方文檔的流程圖中,沒有單獨列出dep
和obvserver
,由於這個流程最核心的思路就是將data的屬性轉化成getter
和setter
而後和watcher
綁定。
而後依然是慣例:若是這篇文章對你有幫助,但願能夠收藏和推薦,以上內容屬於我的看法,若是有不一樣意見,歡迎指出和探討。請尊重做者的版權,轉載請註明出處,如做商用,請與做者聯繫,感謝!