原本vue的響應式應該纔是重中之重。可是網上的文章不少不少。在看computed的實現以前。確定仍是要把vue的響應式如何實現好好看一下。或者說二者根本就是同樣的東西。這邊推薦幾篇文章關於vue的響應式。javascript
vue響應式簡單實現html
vue慕課響應式手記vue
仍是看看官網對於響應式的解釋:
java
總的來講。vue實現響應式的關鍵有三個:watcher,dep,observe;app
observe:遍歷data中的屬性。在get,set方法中設置核心數據劫持異步
dep:每一個屬性都有一個本身的dep(消息訂閱起)用於訂製該屬性上的全部觀察者函數
watcher:觀察者,經過dep實現對響應屬性的監聽觀察。觀察獲得結果後,主動觸發本身的回調oop
能夠去看看vue2.3的這三部分源碼。中間仍是有不少精美的設計。好比一個全局惟一的Dep.target,在任什麼時候候都是惟一的值。以確保同一時間只有一個觀察者在訂閱。再好比,watcher中也會存下相關的訂閱器,實現去重和實現同一個觀察者的分組(這裏是實現computed的關鍵),再如。watcher中的id也會惟一。用於異步更新的時候不一樣時出發相同的訂閱。仔細看看會收穫不小。改天我把全部的響應式的代碼也整理一下。
在理解了響應式的狀況下。咱們來看看computed的實現。最簡單的一個demo以下:ui
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <div id="app"> <div name="test">{{computeA}}</div> </div> </body> <script src="vue.js"></script> <script type="text/javascript"> new Vue({ el: '#app', data: function () { return { firstName: 111, lastName: 222 } }, computed: { computeA: function () { return this.firstName + ' ' + this.lastName } }, created(){ setTimeout( () => { this.firstName = 333; },1000 ) } }) </script> </html>
咱們來從源碼的角度看看發生了什麼:this
在初始化實例建立響應式的時候。對options中的computed作了特殊處理:
function initComputed (vm, computed) { var watchers = vm._computedWatchers = Object.create(null); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; { if (getter === undefined) { warn( ("No getter function has been defined for computed property \"" + key + "\"."), vm ); getter = noop; } } // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);//爲每個computed項目訂製一個watcher // 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); } } function defineComputed (target, key, userDef) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key); sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } Object.defineProperty(target, key, sharedPropertyDefinition); } function createComputedGetter (key) {//構造該computed的get函數 return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate();//收集該watcher的訂閱 } if (Dep.target) { watcher.depend();//同一爲這一組訂閱再加上組件re-render的訂閱(該訂閱負責更新組件) } return watcher.value } } }
總的來講。理解了響應式的構建以後。再來看computed的實現仍是很直觀的。組件初始化的時候。computed項和data中的分別創建響應式。data中的數據直接對屬性的get,set作數據攔截。而computed則創建一個新的watcher,在組件渲染的時候。先touch一下這個computed的getter函數。將這個watcher訂閱起來。這裏至關於這個computed的watcher訂閱了firstname和lastname。touch完後。Dep.target此時又變爲以前那個用於更新組件的。再經過watcher.depend()將這個組統一加上這個訂閱。這樣一旦firstname和lastname變了。同時會觸發兩個訂閱更新。其中一個即是更新組件。從新re-render的函數。感受看的還不夠細啊