用法app
模板內的表達式很是便利,可是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板太重且難以維護,好比:函數
<div id="example">{{ message.split('').reverse().join('') }}</div> <script> var app = new Vue({ el:'#example', data:{message:'hello world'} }) </script>
這樣模板再也不是簡單的聲明式邏輯,必須看一段時間才能意識到,對於這些複雜邏輯,須要使用計算屬性,例如:oop
<div id="example">{{ reversedMessage}}</div> <script> var app = new Vue({ el:'#example', data:{message:'hello world'}, computed:{ reversedMessage:function(){return this.message.split('').reverse().join('')} } }) </script>
在模板中能夠把computed看成data屬性來使用源碼分析
computed是一個對象,每一個鍵是計算屬性的值,值有兩種使用方法:值是一個函數,或者值是一個包含get和set的對象this
源碼分析lua
Vue實例後會先執行_init()進行初始化(4579行)時,會執行initState()進行初始化,以下:spa
function initState (vm) { //第3303行 vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } //若是定義了computed,則調用initComputed初始化computed if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
initComputed函數以下:
var computedWatcherOptions = {lazy: true}; //計算屬性的配置信息 function initComputed (vm, computed) { //第3424行 // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); //定義一個空對象,沒有原型的,用於存儲全部計算屬性對應的watcher // computed properties are just getters during SSR var isSSR = isServerRendering(); for (var key in computed) { //遍歷每一個計算屬性 var userDef = computed[key]; //將計算屬性的值保存到userDef裏面 var getter = typeof userDef === 'function' ? userDef : userDef.get; //若是userDef是一個函數則賦值給getter,不然將userDef.get賦值給getter if ("development" !== 'production' && getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( //建立一個內部的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)) { //若是key在vm中沒有定義 注:組件的計算屬性在模塊加載的時候已經被定義在了原型上面了 defineComputed(vm, key, userDef); //則執行defineComputed()函數 } 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); } } } }
注:對於計算屬性的Watcher來講,它的lazy屬性爲true,所以new watcher()結尾時不會執行get()方法,而是直接返回undefined(在3127行)(求值會等到該計算屬性被調用時才求值的)prototype
回到initComputed()函數,defineComputed()定義以下:設計
function defineComputed ( //第3465行 target, key, userDef ) { var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { //若是userDef爲函數,則默認爲get
sharedPropertyDefinition.get = shouldCache //保存到get裏
? createComputedGetter(key)
: userDef;
sharedPropertyDefinition.set = noop; //將set設爲noop
} else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } if ("development" !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); //設置訪問器屬性,這樣當咱們在模板裏訪問計算屬性時就會執行sharedPropertyDefinition的get方法了 }
初始化完成了,當咱們的模板渲染成render函數時會執行以下函數code
(function anonymous( //這是模板編譯後生成的render函數 ) { with(this){return _c('div',{attrs:{"id":"example"}},[_v(_s(reversedMessage))])} })
、獲取reversedMessage屬性時就會執行到計算屬性的get訪問器屬性,也就是上面在3465行定義的defineComputed裏的訪問器屬性,以下:
function createComputedGetter (key) { //第3498行 return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; //獲取key對應的計算watcher if (watcher) { if (watcher.dirty) { //當watcher.dirty爲true時 watcher.evaluate(); //執行watcher.evaluate()函數 } if (Dep.target) { //這個Dep.target存在(這是個渲染watcher) watcher.depend(); //則執行watcher.depend(); } return watcher.value //最後返回計算屬性的值 } } }
watcher函數對象的evaluate()和depend()對象都是爲計算屬性量身定製的,也就是說是它獨有的,對於evaluate()來講,以下:
Watcher.prototype.evaluate = function evaluate() { //爲計算watcher量身定製的 this.value = this.get(); //調用計算屬性的get方法,此時若是有依賴其餘屬性,則會在其餘屬性的dep對象裏將當前計算watcher做爲訂閱者 this.dirty = false; //修正this.dirty爲false,即一個渲染watcher渲染多個計算屬性時,只會執行一次 };
例子裏執行完evaluate以後,Vue實例data裏的message:'hello world'對應的subs就保存了當前的計算watcher,以下:
這樣當message修改了以後就會觸發計算watcher的更新了,回到createComputedGetter 裏還會執行watcher.depend();,以下:
Watcher.prototype.depend = function depend () { //第3254行 var this$1 = this; var i = this.deps.length; //獲取計算watcher的全部deps while (i--) { this$1.deps[i].depend(); //爲該deps增長渲染watcher } };
執行完後會爲當前計算屬性所依賴的全部其它數據的訂閱者subs裏添加一個渲染watcher,執行完後Vue實例data裏的message:'hello world'對應的subs又保存了當前的渲染watcher,以下:
如今計算watcher依賴的data屬性對應的subs裏存在兩個訂閱者了,當message進行修改時,會分別觸發兩個watcher的更新操做,reversedMessage也會進行相應的更新操做,而後DOM也更新了。