前言:
前言就是有了前幾篇的基礎(chǔ)對(duì)于vue響應(yīng)式原理的初步了解之后,再去看這兩個(gè)東西會(huì)方便很多。寫(xiě)這篇文章是為了一個(gè)梳理,還有一些其他的原因,年底再說(shuō)。
先看computed
computed是在initState的時(shí)候,并且在初始化data之后,進(jìn)行初始化的,來(lái)看看初始化的時(shí)候它干了什么:
function initComputed (vm, computed) { // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering(); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if (getter == null) { warn( ("Getter is missing for computed property "" + key + ""."), vm ); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } else { 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); } } } }
兩參數(shù)vm computed就是當(dāng)前的vm組件和實(shí)例上的computed屬性,說(shuō)得通俗點(diǎn)就是new Vue的時(shí)候?qū)懙哪莻€(gè)computed,然后我直接關(guān)注for in 里的代碼:這里拿到computed上的key和對(duì)應(yīng)的value之后,第一步是取得computed里key對(duì)應(yīng)的value,這里value可能會(huì)是一個(gè)函數(shù),一個(gè)對(duì)象,如果是函數(shù)就直接取,如果是對(duì)象就取他的get方法,然后會(huì)為當(dāng)前的key生成一個(gè)watcher:
watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); //computedWatcherOptions 等于 var computedWatcherOptions = { lazy: true };
這里啊,這個(gè)lazy如果你對(duì)watcher構(gòu)造函數(shù)的源碼模糊了,建議重新打開(kāi)一個(gè)窗口看看,computed從源碼角度來(lái)講,為什么data里的值變了之后才會(huì)跟著變呢,是因?yàn)檫@個(gè)地方,
第二步是判斷當(dāng)前key是否與props或data里邊的key重復(fù),有則警告,沒(méi)有就調(diào)用defineComputed:
function defineComputed ( target, key, userDef ) { var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef); sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop; sharedPropertyDefinition.set = userDef.set || noop; } if (sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property "" + key + "" was assigned to but it has no setter."), this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); } //這里 var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop };
直接看最后一句,vue的老套路了,劫持一個(gè)對(duì)象的屬性的get set,還有一層作用是執(zhí)行vm.key的時(shí)候把這個(gè)操作代理到了當(dāng)前定義的get上,其實(shí)computed的set不怎么常用,那來(lái)看看這里的get是什么:shouldChache是isServerRendering的執(zhí)行結(jié)果取反的結(jié)果,isServerRendering 字如其名自然跟服務(wù)端渲染相關(guān),看源碼的話,當(dāng)在服務(wù)端運(yùn)行的時(shí)候此值才有可能取真值,那這里他就是false,然后,shouldChache自然取true,接著走,然后computed對(duì)應(yīng)的value一般為function,所以這里執(zhí)行createComputedGetter:
function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value } } }
這里實(shí)際是把key閉了一個(gè)包,返回的函數(shù)呢,就是sharedPropertyDefinition的get,所以,此后某個(gè)地方訪問(wèn)vm.key的時(shí)候這個(gè)閉包函數(shù)就會(huì)執(zhí)行,再看一下這個(gè)函數(shù)執(zhí)行的細(xì)節(jié),第一步取得當(dāng)前computed里key對(duì)應(yīng)的watcher實(shí)例,然后判斷watcher.dirty是否為true,這個(gè)dirty和上邊提到的lazy在實(shí)例化watcher的時(shí)候用的是同一個(gè)值,在初始化所有computed的時(shí)候他們都為true,因此第一次渲染的時(shí)候,這個(gè)地方會(huì)執(zhí)行到watcher.evaluate():
Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; };
evaluate很簡(jiǎn)單,計(jì)算value,標(biāo)記dirty為false,這里的get前面我們也應(yīng)該講過(guò):他做了一件事情就是將當(dāng)前watcher pushTarget,然后調(diào)用watcher實(shí)例的getter方法,getter方法就是初始化watcher的時(shí)候傳進(jìn)去的那個(gè)getter,那這個(gè)getter里的東西呢,我們注意到computed里一般都會(huì)訪問(wèn)到當(dāng)前data的get方法,而前幾篇講了在get方法中,我們就能收集到當(dāng)前的watcher,因此此時(shí)就會(huì)完成依賴收集。然后computed就定義完畢了。