計算屬性就是根據必定的邏輯,將一個新屬性與data數據的某個屬性進行關聯,由此獲取與原數據對應的值。
以一個例子來講明:javascript
<div id="test"> 你輸入的:<input type="text" v-model="message"><br/> 將變成:<input type="text" v-model="newMessage" disabled> </div>
let vm = new Vue({ el:'#test', data:{ message:''}, computed:{ newMessage:function(){ return this.message==''?'':this.message+',哈哈!'; }, newMessageForTest:{ get:function(){ return this.message==''?'':this.message+',嘿嘿!'; } } } });
【這裏提供了寫兩種計算屬性的方法。newMessage默認對應的方法爲message的getter方法,而newMessageForTest爲message專門提供了get。】html
計算屬性的做用就是讓 Vue 知道 vm.newMessage 依賴於 vm.message,當 vm.message發生改變時,全部依賴 vm.newMessage 的綁定也會更新。
如圖:vue
function initState (vm) { 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); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
Vue在初始化組件的時候調用initState(),此時若用戶配置了computed,則進入initComputed(vm, opts.computed)對計算屬性進行初始化。java
var watchers = vm._computedWatchers = Object.create(null);
initComputed ()接收vm,computed兩個參數,
首先定義了一個空對象watchers,並賦值給_computedWatchers掛載到vm下。watchers就是以鍵值對的方式,存儲計算屬性對應的方法。express
var isSSR = isServerRendering();
判斷是否是服務器端渲染,計算屬性在服務器渲染的狀況下只有getter。若是是服務器端渲染,Vue不會爲計算屬性添加Watcher。
關於SSR的學習來自:https://codesky.me/archives/h...segmentfault
for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if ("development" !== 'production' && getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } 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); } } }
2. 爲計算屬性建立觀察者Watcher,存放在上面定義的watchers空對象中。 3. 通過判斷後調用defineComputed定義計算屬性。
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; } 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);
defineComputed 接收了target,key,userDef三個參數,分別是該組件,計算屬性,以及計算屬性對應的方法,這段代碼根據組件的不一樣狀態,將計算屬性綁定到組件上。
關於Object.defineProperty學習於https://segmentfault.com/a/11...服務器
function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { watcher.depend(); return watcher.evaluate() } } }
this.dep.depend();
watcher.depend()裏經過this.dep(該依賴)調用depend()。oop
Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } };
而Dep的depend方法調用了Watcher的addDep方法。學習
Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { dep.addSub(this); } } };
addDep將依賴dep push到newDeps中,將dep的id push到newDepIds。dep{id:2,subs:[watcher(計算屬性),watcher2(計算屬性的關聯屬性)]}。this
上圖是message的watcher,對應的expression就是Vue的_update()方法。
這是newMessage的watcher,對應的expression就是用戶自定義的get方法。
當message發生改變時,觸發對應的dep.notify()方法,發佈通知觀察者watcher執行update,以後會觸發watcher的getter方法獲取計算屬性改變後關聯屬性的值,並將新的值更改到watcher對象下,進行數據更新了。
(Watcher.prototype.get):
value = this.getter.call(vm, vm);
(Watcher.prototype.getAndInvoke):
// set new value var oldValue = this.value; this.value = value; this.dirty = false;
watcher.evalute()就是返回掛載到watcher下的新的value。
Watcher.prototype.evaluate = function evaluate () { if (this.dirty) { this.value = this.get(); this.dirty = false; } return this.value };