Vue.js 源碼分析(六) 基礎篇 計算屬性 computed 屬性詳解


模板內的表達式非常便利,但是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護,比如:

    <div id="example">{{ message.split('').reverse().join('') }}</div>
    <script>
        var app = new Vue({
            el:'#example',
            data:{message:'hello world'}
        })
    </script>

這樣模板不再是簡單的聲明式邏輯,必須看一段時間才能意識到,對於這些復雜邏輯,需要使用計算屬性,例如:

    <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的對象

 

 源碼分析


   Vue實例后會先執行_init()進行初始化(4579行)時,會執行initState()進行初始化,如下:

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);
      }
    }
  }
}

writer by:大沙漠 QQ:22969969

 
        

注:對於計算屬性的Watcher來說,它的lazy屬性為true,因此new watcher()結尾時不會執行get()方法,而是直接返回undefined(在3127行)(求值會等到該計算屬性被調用時才求值的)

回到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函數時會執行如下函數

(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也更新了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM