上回提到,computed
————計算屬性的緩存與Watcher
這個類的dirty
屬性有關,那麼此次咱們接着來看下,dirty
屬性到底取決於什麼狀況來變化,從而對computed
進行緩存。
切入正題以前,咱們先來看一個問題:若是一個computed
的結果是受data
屬性下的值影響的,那麼如何去捕獲因某個值變化而引發的computed
的變化?答案是:依賴收集javascript
根據上面的斷點,在update
函數執行以前,咱們注意到,有個reactiveSetter
函數在它以前。咱們點擊右側調用棧中的reactiveSetter
,此時有一個函數特別醒目:defineReactive$$1
,通過又一次的斷點,咱們發現它在幾處都有調用:前端
initRender
函數中調用walk
函數中調用在實際斷點調試的時候,咱們很容易能夠知道存在這樣的,同時也是與本文有關的調用順序(從下往上):java
defineReactive$$1
walk
Observer
observe
initData
initState
Observer
類根據上邊提供的調用順序,咱們重點看一下幾個關鍵的函數:react
observe
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob }
光看註釋咱們都能知道,observe
函數的做用是:爲某個值建立一個observer
實例,隨後將這個observer
實例返回,在這裏起到一個對值進行篩選的做用數組
Observer
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */ var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { this.walk(value); } };
註釋大意:緩存
每一個被觀察的對象都附屬於Observer
類。每次對對象的觀察都會將它的getter
和setter
屬性覆蓋,用以收集依賴以及觸發更新
walk
&& defineReactive$$1
Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i]); } }; /** * Define a reactive property on an Object. */ function defineReactive$$1 ( obj, key, val, customSetter, shallow ) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); } }); }
其中,這端代碼是關鍵:微信
get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value },
若是閱讀了整段defineReactive$$1
函數,那麼很容易就發現,dep
不過是Dep
類new
出來的實例,那麼即便不看Dep.prototype.depend
的實現,你也知道dep.depend()
其實也就是在收集依賴。
另外,這段代碼意味着單單在data
屬性下聲明一個變量是不會進行依賴收集的,須要變量在程序中被調用,那麼纔會被收集到依賴中(其實這也是一種優化)async
Dep
類下的相關實現/** * A dep is an observable that can have multiple * directives subscribing to it. */ var Dep = function Dep () { this.id = uid++; this.subs = []; }; Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
上面說了這麼多未免有點亂,最後從新梳理下computed
實現緩存的思路:函數
Vue
在初始化data
屬性時,會將data
屬性下相關的變量進行觀察(observe
),同時從新設置它的getter
和setter
屬性,以便在其被調用時收集到它的依賴。computed
computed
時判斷this.dirty
屬性,爲true
時調用evaluate
從新計算它的值並將this.dirty
置爲false
,將值存在this.value
上,再調用computed
則直接返回this.value
computed
中依賴的值發生變化時會自動觸發該值的setter
屬性,緊接着調用notify
函數,遍歷一個subs
數組,觸發update
函數將this.dirty
重置爲true
computed
再次被調用時,因爲this.dirty
已是true
,則會從新計算掃描下方的二維碼或搜索「tony老師的前端補習班」關注個人微信公衆號,那麼就能夠第一時間收到個人最新文章。優化