本文是一篇vue.js源碼學習筆記,適合對vue的數據響應實現有必定了解的同窗,文中有表述不許確的地方還望指出。那麼開始了vue
提示:文中會看到( 知識點:xxx )這樣的標記表示在源碼的相應位置打上標記, 方便你們閱讀git
/src/core/instance/state.jsgithub
// initState會在new Vue()時執行
export function initState (vm: Component) {
/*
other
*/
// 若是咱們定義了comouted屬性則執行initComputed
if (opts.computed) initComputed(vm, opts.computed)
/*
other
*/
}
複製代碼
在同一個文件中找到initComputed的定義緩存
function initComputed (vm, computed) {
// 往組件實例上添加一個_computedWatchers屬性,保存全部的computed watcher
const watchers = vm._computedWatchers = Object.create(null)
// 對全部的computed屬性遍歷處理
for (const key in computed) {
// 將咱們定義的computed屬性值用userDef保存
const userDef = computed[key]
// 咱們在定義computed時能夠是一個函數,也能夠是一個對象{get:function(){}, set:function(){}}
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 數據響應過程當中的watcher(注意第二個參數是咱們剛纔拿到的getter,記住了)
watchers[key] = new Watcher(
vm,
getter || noop, // 注意這裏,注意這裏,注意這裏,(****知識點getter)
noop,
computedWatcherOptions
)
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
複製代碼
接下來咱們仍是在這個文件中找到defineComputed的實現bash
export function defineComputed (target, key, userDef) {
/* other */
// 這裏我對源碼進行了簡化
// sharedPropertyDefinition是一個全局對象
// 拿到一個get函數
sharedPropertyDefinition.get = createComputedGetter(key)
/* other */
// 這個函數的主要功能是computed屬性的get進行了重寫
Object.defineProperty(target, key, sharedPropertyDefinition)
}
複製代碼
仍是繼續看createComputedGetter很重要,很重要,很重要函數
function createComputedGetter (key) {
// 返回一個函數,也就是咱們在上一個函數中那個get函數
return function computedGetter () {
// 拿到咱們在initComputed函數中添加到vm上面的_computedWatchers
const watcher = this._computedWatchers && this._computedWatchers[key]
// 若是咱們有定義computed屬性,watcher一定存在
if (watcher) {
// 注意,注意,注意,只要在模板中使用了這個computed屬性,以後每次頁面更新就是循環知識點1到知識點5這個過程
// 第二節主要就是在講這一塊,在理解下面的步驟時能夠對照的看一下
if (watcher.dirty) { // ****標記:知識點1
watcher.evaluate() // ****標記:知識點2
}
if (Dep.target) { // ****標記:知識點3
watcher.depend() // ****標記:知識點4
}
return watcher.value // ****標記:知識點5
}
}
}
複製代碼
仍是在這個文件中咱們能夠找到這個對象oop
const computedWatcherOptions = { lazy: true }
複製代碼
回到initComputed函數中new Watcher時學習
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // 看這裏,看這裏,看這裏(傳入{lazy: true})
)
複製代碼
Watcher的源碼ui
constructor (vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
/* ... */
if (options) {
this.lazy = !!options.lazy // this.lazy = true
}
this.getter = expOrFn
this.dirty = this.lazy // 初始化this.dirty = true
/* ... */
// 注意了,注意了,注意了
// new時this.lazy爲true因此this.value = 'undefined'
this.value = this.lazy ? undefined : this.get()
}
複製代碼
主要流程在createComputedGetter函數裏面this
new Watcher
時 this.dirty = this.lazy
因此知識點1:watcher.dirty = truewatcher.evaluate()
會將this.dirty = false
接着會執行Watcher的this.get()
最終其實就是執行知識點getter:this.getter()
Dep.target
永遠爲真對Watcher的實現這裏就說這麼多了,本文是創建在你們對Wathcer類有必定了解的基礎上講解的。若是你們有須要能夠留言後期會給你們詳細梳理下數據響應的實現過程,網上好像已經有不少相關的文章了
new Vue({
data(){
return {
dataA: 'a',
dataB: 'b'
}
},
template: '<div>{{computedA}}-{{dataB}}</div>',
computed: {
computedA() {
return 'computed ' + this.dataA
}
},
method: {
changeA(){
this.dataA = 'change dataA'
},
changeB(){
this.dataA = 'change dataB'
}
}
})
複製代碼
看在createComputedGetter函數
{{computedA}}
執行computedA.get()
轉到函數createComputedGetter中this.dirty = true
watcher.evaluate()
執行,將this.dirty = false
,watcher內部執行知識點getter:this.getter()
this.getter = computedA = function(){
return 'computed' + this.dataA // 看這裏,看這裏,看這裏,知識點update
}
複製代碼
獲得了wacher.value 等於 computed a
watcher.depend()
從新收集依賴<div>computed a-b</div>
this.changA()
改變dataA,調用dataA中的dep.notify()
,會執行dataA的全部watcher對象wathcer.update()
,由於computed所屬watcher的lazy永遠爲true,知識點1: this.dirty = true{{computedA}}
再次調用computedA.get()
,循環第1步經過第二節3段中computed的響應過程咱們知道,computedA會監聽dataA的改變去改變知識點1: this.dirty = true才最終執行了知識點getter
假設:
this.changeB()
,改變了dataB值,wathcer.update()
,{{computedA}}
再次調用computedA.get()
轉到函數createComputedGetter中。watcher.update()
知識點1:this.dirty = false,最終 並不會執行到知識點:getterreturn watcher.value
由於這篇主要是講computed的知識,對於數據響應的知識沒有詳細說明,可能看起來有點懵逼,後面有須要會詳細分析下vue數據響應的實現過程。