寫在前面:本文爲我的在平常工做和學習中的一些總結,便於後來查漏補缺,非權威性資料,請帶着本身的思考^-^。
前文連接:瞭解一下Vue - [Vue是怎麼實現響應式的(一)]
前言:上一篇文章簡單介紹了基於data的Vue響應式的實現,這篇將進行一點擴展,data變動會自動觸發computed進行從新計算,從而反映到視圖層面,那這個過程又是怎麼作到的呢?react
顧名思義,在initComputed中主要進行的工做是對computed進行初始化,上代碼:segmentfault
function initComputed (vm, computed) { const watchers = vm._computedWatchers = Object.create(null) // 用於存放computed 相關的watcher for (const key in computed) { // 遍歷computed,爲每個computed屬性建立watcher實例,這個watcher實例的做用後面會體現 // 這裏能夠看出,平時咱們的computed通常都是函數形式的,但不少時候咱們也能夠寫成{get() {}, set() {}},這種對象形式 const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, // 此函數參數會在watcher實例的get方法中進行執行 noop, computedWatcherOptions // {dirty: true},能夠翻一下以前的class Watcher代碼或者找源碼看一下,這個options其中的一個做用就在於控制實例化watcher的時候是否先執行一次get() 方法,這個get方法內部會對參數傳進來的getter進行執行 ) if (!(key in vm)) { defineComputed(vm, key, userDef) } } } // function defineComputed function defineComputed ( // 總體來講此函數的做用就是經過defineProperty定義getter/setter將computed中的屬性代理到vm上 target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // 非服務端渲染,則爲true if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) // computed的計算結果會被緩存,不須要每次訪問computed屬性時都從新進行計算 : userDef // computed 不使用緩存的狀況 sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
實例化Vue期間,對computed的處理,作了:緩存
$mount包含了模板編譯、render函數生成... 再也不贅述
和響應式相關的是在$mount中實例化了一個render Watcher,前文已經有過標註,在實例化Watcher中會執行get()函數,從而執行render函數,在render函數的執行過程當中勢必會讀取頁面渲染使用到的computed屬性,觸發其getter:函數
// computed屬性的getter function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] // watcher就是initComputed時傳入computed[key]做爲getter參數實例化的watcher if (watcher) { if (watcher.dirty) { // 若是是首次讀取computed[key],通常watcher.dirty爲true watcher.evaluate() /** evaluate () { this.value = this.get() this.dirty = false // 執行完get以後就會將dirty置爲false,這樣下次讀取computed[key]時就不會再從新計算 } 執行watcher.get(),在這個函數內部會作幾個事情: 執行pushTarget函數,將當前的Dep.target置爲當前watcher實例 執行computed[key],計算獲得結果賦值給watcher.value 若是computed[key]函數內容是經過幾個data計算獲得值,則將會觸發data的getter, 這將會把這個幾個data的dep對象做爲依賴添加至watcher的deps列表,同時將當前watcher添加至這些dep的subs列表中, 通俗一點說,這個過程對於當前watcher來講就是依賴收集過程,將其依賴的項添加至deps中 對於某一個data的dep來講,就是將當前watcher添加至其觀察者列表subs中 執行完以上過程,就會將Dep.target重置爲以前的值, */ } if (Dep.target) { watcher.depend() // 這一步也很重要,此處是將當前watcher的依賴也加入到Dep.target的依賴列表中 /** 爲何要有這一步呢? 由於當前的Dep.target在執行完watcher.evaluate以後就被重置回了上一個Dep.target,通常來講當前的Dep.target 就是render Watcher 設想有這種狀況:某一個data的屬性x並無直接用於render,那麼在render執行過程的依賴收集x就不會被添加 到render Watcher的deps中,x的dep.subs中也沒有render Watcher 也就是說以後若是對x進行從新賦值,則不會 通知render Watcher,此時尚未問題,但時若是x被computed用到,因爲computed沒有setter,則x被從新賦值 通知到computed Watcher去從新計算,可是computed並無直接通知render Watcher的方法,這個時候render就不會 從新執行,頁面也就不會進行更新。。。 */ } return watcher.value } } }
總之,詳見代碼註釋。。。oop
這裏已經知道對data從新賦值,會觸發其對應setter學習
// setter set: function reactiveSetter (newVal) { if (newVal === value || (newVal !== newVal && value !== value)) { return } val = newVal childOb = !shallow && observe(newVal) dep.notify() // 這裏會通知到全部watcher,讓它們進行update } // dep.notify notify () { // 在render階段進行依賴收集時會將watcher加入subs列表,computed在進行計算的時候會收集依賴的data, // 與此同時會將computed的watcher對象添加至data的dep.subs列表 const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
render watcher和computed watcher執行update是不一樣的,computed watcher的update方法只會將watcher.dirty置爲true,這表明該computed依賴的data發生了更新,須要從新計算;這樣在render函數再次執行的時候會讀取computed,觸發computed的getter,在getter中會從新計算得出computed的新值,而且將dirty置爲false,表明只需計算一次,在同一個render loop中屢次引用該computed將不會從新計算。this
THE END...lua