<body> <div id="app"> {{ count }} </div> </body> <script> new Vue({ el: '#app', data () { return { num: 66 } }, computed: { count () { console.log(1) return this.num } }, methods: { add () { setInterval(() => { this.num ++ }, 1000) } }, created () { this.add() } }) </script>
console.log(1)
會間隔的打印出來嗎?{{ count }}
,再問console.log(1)
會間隔的打印出來嗎?watch
監聽count,來打印`console.log(1)watch: { count: function (oldValue, newValue) { } }
如下是個人理解,有誤還請指出,共同進步html
computed
是惰性求值,在new watcher時是計算屬性時,this.value=undefined
因此一開始不會觸發get進行依賴收集即僅僅定義computed
的話是沒有進行計算屬性count
的依賴收集(能夠相似當作data中的數值,僅僅進行了響應式get,set
的定義,並無觸發dep.depend
,因此當值發生變化的時候,他並不知道要通知誰,也就不會執行相應的回調函數了)源碼中有這麼一段:vue
depend () { if (this.dep && Dep.target) { //由於惰性求值,因此Dep.target爲false this.dep.depend() } }
因此若是僅僅是computed
的初始化的話並Dep.target
就是undefined
,因此實例化的watch
並不會加入dep的中express
function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) //(標記1)新建一個沒有原型鏈的對象,用來存`computed`對象每一個值的watch實例對象 const isSSR = isServerRendering() //與服務端渲染有關,暫時忽略 for (const key in computed) { const userDef = computed[key] //取key的值,該值大部分是function類型 //下面主要做用就是在非生產環境中沒有getter,保警告 const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } } if (!isSSR) { //computed中不一樣的key,也就是計算屬性生成watch實例, //watch做用:簡單看就是當值發生變化時會觸通知到watch,觸發更新,執行回調函數 watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) { //做用是將{key: userDef}變成響應式,重寫其get和set defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } }
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef 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) }
上面函數的做用就是改寫get與set
,關鍵就是這個createComputedGetter
在作什麼?
早版本createComputedGetter
的實現是:數組
function createComputedGetter(){ return function computedGetter () { //這個就是以前用來收集watch實例的一個對象,可看註釋:標記1 const watcher = this._computedWatchers && this._computedWatchers[key] if(watcher) { if(watcher.dirty) { watcher.evaluate() } if(Dep.target){ //這裏也能夠看出Dep.target爲false時是不會觸發depend,即添加依賴 watcher.depend() } return watcher.value } } }
export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { //進行初始化的定義,忽略無關代碼 if(options) { this.lazy = !!options.lazy }else { this.lazy = false } this.getter = parsePath(expOrFn) //返回一個取data值得函數 this.dirty = this.lazy //true this.value = this.lazy ? undefined : this.get() //undefined,當不會執行get時也就不會觸發get實例方法中的depend的了 } get () { // 僞代碼 Dep.target = this //取值也就是訪問觸發屬性的get,get中又觸發dep.depend(),而dep.depend內部觸發的是Dep.target.addDep(this),這裏的this實際上是Dep實例 let value = this.getter.call(vm, vm) Dep.target = undefined } addDep (dep: Dep) { //僞代碼 const id = dep.id if(!this.depIds.has(id)) { this.depIds.add(id) this.deps.push(dep) dep.addSub(this) //this是watch實例對象 } } update () { // 省略... } getAndInvoke (cb: Function) { // 省略... } evaluate () { this.value = this.get() this.dirty = false } depend () { let i = this.deps.length while(i --) { this.deps[i].depend() } } ... }
總結: 1.watcher.dirty
默認爲true,執行watcher.evaluate()
因此computed第一次默認會渲染,與watch不一樣;2.當默認渲染,觸發了get,Dep.target
就不是false,就會執行watcher.depend()
app
return this.num + this.num1
,當讀取計算屬性時會分別觸發num與num1
的get,get中又觸發dep.depend(),而dep.depend內部觸發的是Dep.target.addDep(this),這裏的this實際上是Dep實例,這樣就會分別將不一樣編號的num與num1
的dep,加入到deps中,最後將計算屬性的依賴加入到num,num1
的Dep中,this.deps[i].depend()
也會加,但以前已加入改id因此猜想會直接return掉num
發生改變,觸發set
,觸發其notify
方法即遍歷dep.subDeps數組(subDeps中放的是各類依賴),觸發依賴的update方法。但以前的update方法看了一下update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } }
能夠看出直接走queueWatcher(this)
因此就算內容沒有變化,也會走渲染流程,這就形成了浪費函數
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { watcher.depend() return watcher.evaluate() } } }
if (this.dep && Dep.target) { this.dep.depend() } }
上面這裏的dep又是哪裏來的呢?在watch類中加了下面代碼oop
if (this.computed) { this.value = undefined this.dep = new Dep() //相似一個Object對象,進行observer設置get,set響應式時會進let dep = new Dep, 來收集改值得依賴 } else { this.value = this.get() }
因此從上面的實現能夠看出,對當前計算屬性自身也生成一個dep列表進行收集;徹底能夠把一個computed的初始化看出data中數據的初始化,只不過該值又依賴多個依賴this
evaluate () { if (this.dirty) { this.value = this.get() this.dirty = false } return this.value }
update () { /* istanbul ignore else */ if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true } else { this.getAndInvoke(() => { this.dep.notify() }) } } else if (this.sync) { this.run() } else { queueWatcher(this) } }, //當計算屬性的值發生變化時,改觸發回調函數或者進行渲染,而不是經過以前值(例如num改變)變化就觸發回調 getAndInvoke (cb: Function) { const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { const oldValue = this.value this.value = value this.dirty = false if (this.user) { try { cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { cb.call(this.vm, value, oldValue) } } }
當觸發update時首先經過getAndInvoke
函數進行值得比較,看是否發生變化,即只有在變化時纔會執行,執行的是this.dep.notify()
,而這邊打的this
是當前watch實例對象;由於以前就添加了依賴this.dep.depend()
因此接着觸發cb.call(this.vm, value, oldValue)
cb是:this.dep.notify()
但this指向了vm
用來觸發渲染更新lua
createComputedGetter
函數,就會去取值this.value = this.get()
,進行第一次渲染或取值;同時watcher.depend()
,將計算屬性的依賴
添加至dep中,watch.update
,首先判斷是否存在依賴,存在則只需watcher.getAndInvoke(cb)
,