寫在前面:本文爲我的在平常工做和學習中的一些總結,便於後來查漏補缺,非權威性資料,請帶着本身的思考^-^。node
提及響應式,首先會想到Vue實例中的data屬性,例如:對data中的某一屬性從新賦值,若是該屬性用在了頁面渲染上面,則頁面會自動進行從新渲染,這裏就以data做爲切入點,來看一下Vue中的響應式是怎樣的一個實現思路。react
在建立Vue實例的時候,執行到了一個核心方法:initState,該方法會對methods/props/methods/data/computed/watch進行初始化,此時咱們只關注data的初始化:express
function initData(vm) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} ... const keys = Object.keys(data) let i = keys.length while (i--) { const key = keys[i] ... proxy(vm, '_data', key) ... } ... observe(data, true) }
代碼中省略了一些和當前研究的內容無關的代碼,用...表示;
能夠看到這個方法主要作了兩件事:數組
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) }
observe代碼:函數
observe (value: any, asRootData: ?boolean): Observer | void { let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( observerState.shouldConvert && // 新添加屬性轉爲reactive !isServerRendering() && // 非服務端渲染 (Array.isArray(value) || isPlainObject(value)) && // 數組或者對象 Object.isExtensible(value) && // 可擴展對象 !value._isVue // 非Vue實例 ) { ob = new Observer(value) // 建立Observer實例 } if (asRootData && ob) { ob.vmCount++ } return ob } class Observer { constructor (value: any) { this.value = value this.dep = new Dep() def(value, '__ob__', this) // value.__ob__ = this, 且__ob__爲不可枚舉屬性 if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) // value.__proto__ = Array.prototype this.observeArray(value) } else { this.walk(value) } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } } function defineReactive ( obj: Object, // vm instance of Vue key: string, // '$attr' 等屬性名 val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() let childOb = !shallow && observe(val) // 對當前屬性的值繼續進行observe Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... }, set: function reactiveSetter (newVal) { ... } }) }
仍然逃脫不了粘貼代碼,可是找不出比代碼更直觀的解釋了...
不過核心方法是defineReactive,它仍然是使用defineProperty對vm._data中的每個屬性設置了getter/setter,至於getter/setter中的內容,先不去管他。
到了這裏,對於data的初始化已經告一段落。oop
這裏是Vue.prototype.$mount的執行階段,此階段其實包含了對於模板的編譯、對編譯結果進行轉化生成render函數、render函數的執行進行掛載
這個階段對於data的操做只存在於render函數的執行進行掛載時,核心函數的執行:new Watcher(vm, updateComponent, noop, null, true)
Watcher代碼:性能
class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, // 回調函數 options?: ?Object, isRenderWatcher?: boolean // render時實例的Watcher ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) 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 this.deps = [] // 依賴列表 this.newDeps = [] // 新的依賴列表 this.depIds = new Set() // 依賴ids this.newDepIds = new Set() // 新的依賴ids // parse expression for getter // 將表達式 expOrFn包裝爲getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. * 源碼其實已經給出了註釋,這裏是進行render和依賴收集 */ get () { pushTarget(this) // 爲Dep.target賦值爲當前Watcher實例 let value const vm = this.vm try { value = this.getter.call(vm, vm) // 這裏是render函數 } catch (e) { ... } finally { if (this.deep) { traverse(value) } popTarget() // 彈出Deptarget this.cleanupDeps() // 本次添加的依賴落入this.deps,同時清空this.newDeps } return value }, addDep (dep: Dep) { // 將Dep實例添加至this.newDeps隊列中,這裏的Dep實例產生自經過defineReactive爲data屬性定義getter/setter時,也就是說這裏的Dep實例對應一個data屬性 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) } } } ... }
從上面的代碼能夠看出,響應式相關的核心在於所謂的「依賴收集」,也就是在render函數執行的過程當中勢必會對頁面渲染須要的data屬性進行讀取,這就觸發了響應data屬性的getter,還記得以前省略掉的observe函數中執行defineReactive函數時有關data屬性getter函數相關的代碼嗎?學習
defineReactive ( obj: Object, // vm instance of Vue key: string, // '$attr' 等屬性名 val: any, customSetter?: ?Function, shallow?: boolean ) { ... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 在$mount函數中new Watcher進行依賴收集的時候已經爲Dep.target賦值爲Watcher實例 dep.depend() // 這裏的Dep實例對應當前data屬性,此處會將當前dep實例放入watcher的依賴列表中 if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { // setter相關代碼 ... } }) } // dep.depend 代碼: ... depend () { if (Dep.target) { // 此處Dep.target已被賦值爲Watcher實例 Dep.target.addDep(this) } } ...
頁面的首次渲染基本上包含上述兩個大的過程,這裏先主要基於data進行討論
new Vue(options)中主要作了:ui
$mount中主要作了:
在getter中將當前屬性對應的dep實例添加至Watcher實例的deps列表中,同時將Watcher實例添加進dep的subs觀察者列表中;
爲何data屬性變化了,頁面會從新渲染獲得更新呢?前面作了不少鋪墊,接下來看一下data屬性的變動會進行哪些操做
還記得前面提到得經過defineReactive函數爲vm._data[key]設置得setter嗎?當data變化時會觸發該setter
... set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { // 若是更新先後值相同,則直接返回 return } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 若是newVal爲引用類型,則對其屬性也進行劫持 dep.notify() // 這裏纔是屬性更新觸發操做得核心,它會通知Watcher進行相應得更新 } ... // dep.notify方法 ... notify () { const subs = this.subs.slice() // 這裏存放的是觀察者Watcher列表 for (let i = 0, l = subs.length; i < l; i++) { // 通知每個Watcher,執行其update方法,進行相應更新 subs[i].update() } } ... // Watcher.prototype.update方法 ... update () { if (this.lazy) { this.dirty = true } else if (this.sync) { // 同步更新 this.run() } else { queueWatcher(this) // 這個方法是nextTick中去執行Watcher.prototype.run方法,也就是說data屬性更新觸發setter而後通知Watcher去update這個過程一般並不是同步執行的,而是會先被放入一個隊列,異步執行,落地到咱們使用中:咱們不用擔憂同時修改多個data屬性帶來嚴重的性能問題,由於其觸發的更新並不是同步執行的;還有一點是Watcher.prototype.run方法中會執行get方法(還記得在首次渲染進行依賴收集的時候有這個方法嗎?)該方法中會執行render進行vnode生成,固然會訪問到data中的屬性,這樣就是一個依賴更新的過程,是否是一個閉環? } } ...
queueWatcher(this)
這個方法是nextTick中去執行Watcher.prototype.run方法,也就是說data屬性更新觸發setter而後通知Watcher去update這個過程一般並不是同步執行的,而是會先被放入一個隊列,異步執行,落地到咱們使用中:咱們不用擔憂同時修改多個data屬性帶來嚴重的性能問題,由於其觸發的更新並不是同步執行的;
還有一點是Watcher.prototype.run方法中會執行get方法(還記得在首次渲染進行依賴收集的時候有這個方法嗎?)該方法中會執行render進行vnode生成,固然會訪問到data中的屬性,這樣就是一個依賴更新的過程,是否是一個閉環?
另外不能忽略的一點是,在這個方法執行中還會觸發updated 鉤子函數,固然這裏不作深刻研究,只作一個大體瞭解,由於Vue中的細節不少,可是它不影響咱們瞭解主要流程。
放兩張在debugg源碼時寫的兩張圖,只有本身能看懂當初想到哪裏。。。
THE END