您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue響應式數據中的觀察者模式實例簡析”,在日常操作中,相信很多人在Vue響應式數據中的觀察者模式實例簡析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Vue響應式數據中的觀察者模式實例簡析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
初始化 Vue 實例
在閱讀源碼時,因為文件繁多,引用復雜往往使我們不容易抓住重點,這里我們需要找到一個入口文件,從 Vue
構造函數開始,拋開其他無關因素,一步步理解響應式數據的實現原理。首先我們找到 Vue
構造函數:
// src/core/instance/index.js 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) }
// src/core/instance/init.js Vue.prototype._init = function (options) { ... // a flag to avoid this being observed vm._isVue = true // merge options // 初始化vm實例的$options if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } ... initLifecycle(vm) // 梳理實例的parent、root、children和refs,并初始化一些與生命周期相關的實例屬性 initEvents(vm) // 初始化實例的listeners initRender(vm) // 初始化插槽,綁定createElement函數的vm實例 callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) // 掛載組件到節點 } }
為了方便閱讀,我們去除了 flow
類型檢查和部分無關代碼。可以看到,在實例化Vue組件時,會調用 Vue.prototype._init
,而在方法內部,數據的初始化操作主要在 initState
(這里的 initInjections
和 initProvide
與 initProps
類似,在理解了 initState
原理后自然明白),因此我們重點來關注 initState
。
// src/core/instance/state.js export function initState (vm) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
首先初始化了一個 _watchers
數組,用來存放 watcher
,之后根據實例的 vm.$options
,相繼調用 initProps
、 initMethods
、 initData
、 initComputed
和 initWatch
方法。
initProps
function initProps (vm, propsOptions) { 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) ... defineReactive(props, key, value) if (!(key in vm)) { proxy(vm, '_props', key) } } toggleObserving(true) }
在這里, vm.$options.propsData
是通過父組件傳給子組件實例的數據對象,如 <my-element :item="false"></my-element>
中的 {item: false}
,然后初始化 vm._props
和 vm.$options._propKeys
分別用來保存實例的 props
數據和 keys
,因為子組件中使用的是通過 proxy
引用的 _props
里的數據,而不是父組件傳遞的 propsData
,所以這里緩存了 _propKeys
,用來 updateChildComponent
時能更新 vm._props
。接著根據 isRoot
是否是根組件來判斷是否需要調用 toggleObserving(false)
,這是一個全局的開關,來控制是否需要給對象添加 __ob__
屬性。這個相信大家都不陌生,一般的組件的 data
等數據都包含這個屬性,這里先不深究,等之后和 defineReactive
時一起講解。因為 props
是通過父傳給子的數據,在父元素 initState
時已經把 __ob__
添加上了,所以在不是實例化根組件時關閉了這個全局開關,待調用結束前在通過 toggleObserving(true)
開啟。
之后是一個 for
循環,根據組件中定義的 propsOptions
對象來設置 vm._props
,這里的 propsOptions
就是我們常寫的
export default { ... props: { item: { type: Object, default: () => ({}) } } }
循環體內,首先
const value = validateProp(key, propsOptions, propsData, vm)
validateProp
方法主要是校驗數據是否符合我們定義的 type
,以及在 propsData
里未找到 key
時,獲取默認值并在對象上定義 __ob__
,最后返回相應的值,在這里不做展開。
這里我們先跳過 defineReactive
,看最后
if (!(key in vm)) { proxy(vm, '_props', key) }
其中 proxy
方法:
function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
在 vm
不存在 key
屬性時,通過 Object.defineProperty
使得我們能通過 vm[key]
訪問到 vm._props[key]
。
defineReactive
在 initProps
中,我們了解到其首先根據用戶定義的 vm.$options.props
對象,通過對父組件設置的傳值對象 vm.$options.propsData
進行數據校驗,返回有效值并保存到 vm._props
,同時保存相應的 key
到 vm.$options._propKeys
以便進行子組件的 props
數據更新,最后利用 getter/setter
存取器屬性,將 vm[key]
指向對 vm._props[key]
的操作。但其中跳過了最重要的 defineReactive
,現在我們將通過閱讀 defineReactive
源碼,了解響應式數據背后的實現原理。
// src/core/observer/index.js export function defineReactive ( obj, key, val, customSetter, shallow ) { 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) ... }
首先 const dep = new Dep()
實例化了一個 dep
,在這里利用閉包來定義一個依賴項,用以與特定的 key
相對應。因為其通過 Object.defineProperty
重寫 target[key]
的 getter/setter
來實現數據的響應式,因此需要先判斷對象 key
的 configurable
屬性。接著
if ((!getter || setter) && arguments.length === 2) { val = obj[key] }
arguments.length === 2
意味著調用 defineReactive
時未傳遞 val
值,此時 val
為 undefined
,而 !getter || setter
判斷條件則表示如果在 property
存在 getter
且不存在 setter
的情況下,不會獲取 key
的數據對象,此時 val
為 undefined
,之后調用 observe
時將不對其進行深度觀察。正如之后的 setter
訪問器中的:
if (getter && !setter) return
此時數據將是只讀狀態,既然是只讀狀態,則不存在數據修改問題,繼而無須深度觀察數據以便在數據變化時調用觀察者注冊的方法。
Observe
在 defineReactive
里,我們先獲取了 target[key]
的 descriptor
,并緩存了對應的 getter
和 setter
,之后根據判斷選擇是否獲取 target[key]
對應的 val
,接著是
let childOb = !shallow && observe(val)
根據 shallow
標志來確定是否調用 observe
,我們來看下 observe
函數:
// src/core/observer/index.js export function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } let ob 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 }
首先判斷需要觀察的數據是否為對象以便通過 Object.defineProperty
定義 __ob__
屬性,同時需要 value
不屬于 VNode
的實例( VNode
實例通過 Diff
補丁算法來實現實例對比并更新)。接著判斷 value
是否已有 __ob__
,如果沒有則進行后續判斷:
shouldObserve:全局開關標志,通過toggleObserving來修改。
!isServerRendering():判斷是否服務端渲染。
(Array.isArray(value) || isPlainObject(value)):數組和純對象時才允許添加__ob__進行觀察。
Object.isExtensible(value):判斷value是否可擴展。
!value._isVue:避免Vue實例被觀察
滿足以上五個條件時,才會調用 ob = new Observer(value) ,接下來我們要看下 Observer 類里做了哪些工作
// src/core/observer/index.js export class Observer { constructor (value) { 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) { 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) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
構造函數里初始化了 value 、 dep 和 vmCount 三個屬性,為 this.value 添加 __ob__ 對象并指向自己,即 value.__ob__.value === value ,這樣就可以通過 value 或 __ob__ 對象取到 dep 和 value 。 vmCount 的作用主要是用來區分是否為 Vue 實例的根 data , dep 的作用這里先不介紹,待與 getter/setter 里的 dep 一起解釋。
接著根據 value 是數組還是純對象來分別調用相應的方法,對 value 進行遞歸操作。當 value 為純對象時,調用 walk 方法,遞歸調用 defineReactive 。當 value 是數組類型時,首先判斷是否有 __proto__ ,有就使用 __proto__ 實現原型鏈繼承,否則用 Object.defineProperty 實現拷貝繼承。其中繼承的基類 arrayMethods 來自 src/core/observer/array.js :
// src/core/observer/array.js const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
這里為什么要對數組的實例方法進行重寫呢?代碼里的 methodsToPatch 這些方法并不會返回新的數組,導致無法觸發 setter ,因而不會調用觀察者的方法。所以重寫了這些變異方法,使得在調用的時候,利用 observeArray 對新插入的數組元素添加 __ob__ ,并能夠通過 ob.dep.notify 手動通知對應的被觀察者執行注冊的方法,實現數組元素的響應式。
if (asRootData && ob) { ob.vmCount++ }
最后添加這個 if 判斷,在 Vue 實例的根 data 對象上,執行 ob.vmCount++ ,這里主要為了后面根據 ob.vmCount 來區分是否為根數據,從而在其上執行 Vue.set 和 Vue.delete 。
getter/setter
在對 val 進行遞歸操作后(假如需要的話),將 obj[key] 的數據對象封裝成了一個被觀察者,使得能夠被觀察者觀察,并在需要的時候調用觀察者的方法。這里通過 Object.defineProperty 重寫了 obj[key] 的訪問器屬性,對 getter/setter 操作做了攔截處理, defineReactive 剩余的代碼具體如下:
... 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) { ... childOb = !shallow && observe(newVal) dep.notify() } })
首先在 getter 調用時,判斷 Dep.target 是否存在,若存在則調用 dep.depend 。我們先不深究 Dep.target ,只當它是一個觀察者,比如我們常用的某個計算屬性,調用 dep.depend 會將 dep 當做計算屬性的依賴項存入其依賴列表,并把這個計算屬性注冊到這個 dep 。這里為什么需要互相引用呢?這是因為一個 target[key] 可以充當多個觀察者的依賴項,同時一個觀察者可以有多個依賴項,他們之間屬于多對多的關系。這樣當某個依賴項改變時,我們可以根據 dep 里維護的觀察者,調用他們的注冊方法。現在我們回過頭來看 Dep :
// src/core/observer/dep.js export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
構造函數里,首先添加一個自增的 uid 用以做 dep 實例的唯一性標志,接著初始化一個觀察者列表 subs ,并定義了添加觀察者方法 addSub 和移除觀察者方法 removeSub 。可以看到其在 getter 中調用的 depend 會將當前這個 dep 實例添加到觀察者的依賴項,在 setter 里調用的 notify 會執行各個觀察者注冊的 update 方法, Dep.target.addDep 這個方法將在之后的 Watcher 里進行解釋。簡單來說就是會在 key 的 getter 觸發時進行 dep 依賴收集到 watcher 并將 Dep.target 添加到當前 dep 的觀察者列表,這樣在 key 的 setter 觸發時,能夠通過觀察者列表,執行觀察者的 update 方法。
當然,在 getter 中還有如下幾行代碼:
if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } }
這里可能會有疑惑,既然已經調用了 dep.depend ,為什么還要調用 childOb.dep.depend ?兩個 dep 之間又有什么關系呢?
其實這兩個 dep 的分工是不同的。對于數據的增、刪,利用 childOb.dep.notify 來調用觀察者方法,而對于數據的修改,則使用的 dep.notify ,這是因為 setter 訪問器無法監聽到對象數據的添加和刪除。舉個例子:
const data = { arr: [{ value: 1 }], } data.a = 1; // 無法觸發setter data.arr[1] = {value: 2}; // 無法觸發setter data.arr.push({value: 3}); // 無法觸發setter data.arr = [{value: 4}]; // 可以觸發setter
還記得 Observer 構造函數里針對數組類型 value 的響應式轉換嗎?通過重寫 value 原型鏈,使得對于新插入的數據:
if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify()
將其轉換為響應式數據,并通過 ob.dep.notify 來調用觀察者的方法,而這里的觀察者列表就是通過上述的 childOb.dep.depend 來收集的。同樣的,為了實現對象新增數據的響應式,我們需要提供相應的 hack 方法,而這就是我們常用的 Vue.set/Vue.delete 。
// src/core/observer/index.js export function set (target: Array<any> | Object, key: any, val: any): any { ... if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val }
判斷value是否為數組,如果是,直接調用已經hack過的splice即可。
是否已存在key,有的話說明已經是響應式了,直接修改即可。
接著判斷target.__ob__是否存在,如果沒有說明該對象無須深度觀察,設置返回當前的值。
最后,通過defineReactive來設置新增的key,并調用ob.dep.notify通知到觀察者。
現在我們了解了 childOb.dep.depend() 是為了將當前 watcher 收集到 childOb.dep ,以便在增、刪數據時能通知到 watcher 。而在 childOb.dep.depend() 之后還有:
if (Array.isArray(value)) { dependArray(value) }
/** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }
在觸發 target[key] 的 getter 時,如果 value 的類型為數組,則遞歸將其每個元素都調用 __ob__.dep.depend ,這是因為無法攔截數組元素的 getter ,所以將當前 watcher 收集到數組下的所有 __ob__.dep ,這樣當其中一個元素觸發增、刪操作時能通知到觀察者。比如:
const data = { list: [[{value: 0}]], }; data.list[0].push({value: 1});
這樣在 data.list[0].__ob__.notify 時,才能通知到 watcher 。
target[key] 的 getter 主要作用:
將 Dep.target 收集到閉包中 dep 的觀察者列表,以便在 target[key] 的 setter 修改數據時通知觀察者
根據情況對數據進行遍歷添加 __ob__ ,將 Dep.target 收集到 childOb.dep 的觀察者列表,以便在增加/刪除數據時能通知到觀察者
通過 dependArray 將數組型的 value 遞歸進行觀察者收集,在數組元素發生增、刪、改時能通知到觀察者
target[key] 的 setter 主要作用是對新數據進行觀察,并通過閉包保存到 childOb 變量供 getter 使用,同時調用 dep.notify 通知觀察者,在此就不再展開。
Watcher
在前面的篇幅中,我們主要介紹了 defineReactive 來定義響應式數據:通過閉包保存 dep 和 childOb ,在 getter 時來進行觀察者的收集,使得在數據修改時能觸發 dep.notify 或 childOb.dep.notify 來調用觀察者的方法進行更新。但具體是如何進行 watcher 收集的卻未做過多解釋,現在我們將通過閱讀 Watcher 來了解觀察者背后的邏輯。
function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } ... } }
這是 Vue 計算屬性的初始化操作,去掉了一部分不影響的代碼。首先初始化對象 vm._computedWatchers 用以存儲所有的計算屬性, isSSR 用以判斷是否為服務端渲染。再根據我們編寫的 computed 鍵值對循環遍歷,如果不是服務端渲染,則為每個計算屬性實例化一個 Watcher ,并以鍵值對的形式保存到 vm._computedWatchers 對象,接下來我們主要看下 Watcher 這個類。
Watcher 的構造函數
構造函數接受5個參數,其中當前 Vue 實例 vm 、求值表達式 expOrFn (支持 Function 或者 String ,計算屬性中一般為 Function ),回調函數 cb 這三個為必傳參數。設置 this.vm = vm 用以后續綁定 this.getter 的執行環境,并將 this 推入 vm._watchers ( vm._watchers 用以維護實例 vm 中所有的觀察者),另外根據是否為渲染觀察者來賦值 vm._watcher = this (常用的 render 即為渲染觀察者)。接著根據 options 進行一系列的初始化操作。其中有幾個屬性:
this.lazy:設置是否懶求值,這樣能保證有多個被觀察者發生變化時,能只調用求值一次。
this.dirty:配合this.lazy,用以標記當前觀察者是否需要重新求值。
this.deps、this.newDeps、this.depIds、this.newDepIds:用以維護被觀察對象的列表。
this.getter:求值函數。
this.value:求值函數返回的值,即為計算屬性中的值。
Watcher 的求值
因為計算屬性是惰性求值,所以我們繼續看 initComputed 循環體:
if (!(key in vm)) { defineComputed(vm, key, userDef) }
defineComputed 主要將 userDef 轉化為 getter/setter 訪問器,并通過 Object.defineProperty 將 key 設置到 vm 上,使得我們能通過 this[key] 直接訪問到計算屬性。接下來我們主要看下 userDef 轉為 getter 中的 createComputedGetter 函數:
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 } } }
利用閉包保存計算屬性的 key ,在 getter 觸發時,首先通過 this._computedWatchers[key] 獲取到之前保存的 watcher ,如果 watcher.dirty 為 true 時調用 watcher.evaluate (執行 this.get() 求值操作,并將當前 watcher 的 dirty 標記為 false ),我們主要看下 get 操作:
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (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 }
可以看到,求值時先執行 pushTarget(this) ,通過查閱 src/core/observer/dep.js ,我們可以看到:
Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
pushTarget 主要是把 watcher 實例進棧,并賦值給 Dep.target ,而 popTarget 則相反,把 watcher 實例出棧,并將棧頂賦值給 Dep.target 。 Dep.target 這個我們之前在 getter 里見到過,其實就是當前正在求值的觀察者。這里在求值前將 Dep.target 設置為 watcher ,使得在求值過程中獲取數據時觸發 getter 訪問器,從而調用 dep.depend ,繼而執行 watcher 的 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) } } }
先判斷 newDepIds 是否包含 dep.id ,沒有則說明尚未添加過這個 dep ,此時將 dep 和 dep.id 分別加到 newDepIds 和 newDeps 。如果 depIds 不包含 dep.id ,則說明之前未添加過此 dep ,因為是雙向添加的(將 dep 添加到 watcher 的同時也需要將 watcher 收集到 dep ),所以需要調用 dep.addSub ,將當前 watcher 添加到新的 dep 的觀察者隊列。
if (this.deep) { traverse(value) }
再接著根據 this.deep 來調用 traverse 。 traverse 的作用主要是遞歸遍歷觸發 value 的 getter ,調用所有元素的 dep.depend() 并過濾重復收集的 dep 。最后調用 popTarget() 將當前 watcher 移出棧,并執行 cleanupDeps :
cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } ... }
遍歷 this.deps ,如果在 newDepIds 中不存在 dep.id ,則說明新的依賴里不包含當前 dep ,需要到 dep 的觀察者列表里去移除當前這個 watcher ,之后便是 depIds 和 newDepIds 、 deps 和 newDeps 的值交換,并清空 newDepIds 和 newDeps 。到此完成了對 watcher 的求值操作,同時更新了新的依賴,最后返回 value 即可。
回到 createComputedGetter 接著看:
if (Dep.target) { watcher.depend() }
當執行計算屬性的 getter 時,有可能表達式中還有別的計算屬性依賴,此時我們需要執行 watcher.depend 將當前 watcher 的 deps 添加到 Dep.target 即可。最后返回求得的 watcher.value 即可。
總的來說我們從 this[key] 觸發 watcher 的 get 函數,將當前 watcher 入棧,通過求值表達式將所需要的依賴 dep 收集到 newDepIds 和 newDeps ,并將 watcher 添加到對應 dep 的觀察者列表,最后清除無效 dep 并返回求值結果,這樣就完成了依賴關系的收集。
Watcher 的更新
以上我們了解了 watcher 的依賴收集和 dep 的觀察者收集的基本原理,接下來我們了解下 dep 的數據更新時如何通知 watcher 進行 update 操作。
notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
首先在 dep.notify 時,我們將 this.subs 拷貝出來,防止在 watcher 的 get 時候 subs 發生更新,之后調用 update 方法:
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } }
如果是 lazy ,則將其標記為 this.dirty = true ,使得在 this[key] 的 getter 觸發時進行 watcher.evaluate 調用計算。
如果是 sync 同步操作,則執行 this.run ,調用 this.get 求值和執行回調函數 cb 。
否則執行 queueWatcher ,選擇合適的位置,將 watcher 加入到隊列去執行即可,因為和響應式數據無關,故不再展開。
小結
因為篇幅有限,只對數據綁定的基本原理做了基本的介紹,在這畫了一張簡單的流程圖來幫助理解 Vue 的響應式數據,其中省略了一些 VNode 等不影響理解的邏輯及邊界條件,盡可能簡化地讓流程更加直觀:
到此,關于“Vue響應式數據中的觀察者模式實例簡析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。