Vue源碼解讀之computed計算屬性

什麼是計算屬性?

計算屬性就是根據必定的邏輯,將一個新屬性與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

clipboard.png

用源碼說話

initComputed

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);
      }
    }
  }
  1. 遍歷用戶定義的全部計算屬性,建立變量userDef存儲該計算屬性的get方法。若是用戶用下圖newMessageForTest的方式定義計算屬性,userDef存儲的就是用戶定義的get方法,若是是用下圖newMessage的方式定義計算屬性,Vue就把該計算屬性對應的function做爲它的getter。

clipboard.png

2. 爲計算屬性建立觀察者Watcher,存放在上面定義的watchers空對象中。
3. 通過判斷後調用defineComputed定義計算屬性。

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...服務器

createComputedGetter

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      watcher.depend();
      return watcher.evaluate()
    }
  }
}
  1. 經過計算屬性key的值找到對應的watcher,並啓動該觀察者進行觀察。
  2. 經過watcher.depend()依賴收集綁定,本文就是將message和newMessag綁定。
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

clipboard.png

上圖是message的watcher,對應的expression就是Vue的_update()方法。

clipboard.png
這是newMessage的watcher,對應的expression就是用戶自定義的get方法。

watcher.depend和evalute

當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
};
相關文章
相關標籤/搜索