1. 程式人生 > >Vue原始碼學習筆記

Vue原始碼學習筆記

最近偷懶好久沒有寫部落格了,一直想繼續Vue學習系列,想深入Vue原始碼來寫。結果發現自己層次不夠,對js的理解差好多。所以一直想寫一直擱置著。最近重新振作決心看完Vue原始碼,並且以我們這類前端小白的角度來一步步弄懂Vue原始碼。

PS:以下文章為筆記類,記錄了本人在看原始碼過程中的一些問題和感悟。

Vue原始碼的本質是什麼

Vue.js 本質上就是一個包含各種邏輯的一個function。而我們通常初始化Vue的過程就是例項化的過程。

var vm = new Vue({})

話不多說,老規矩用程式碼說話!
讓我們來對Vue進行列印:

var vm = new Vue({
  ...
}) console.log(Vue) console.log(vm)

列印結果如圖:
vue

這裡可以看到Vue3VueVue4

    function Vue$4(options) {
        this.options = options
    }

    Vue$4.prototype.name = "小東西"
    Vue$4.prototype.age = 27

    console.log(new Vue$4("很好"))

顯示結果如圖
vue$4

綜上所述,Vue物件的本質就是一個function,與我們的Vue$4的不同之處只在於邏輯的多與少。

必須理解Object物件

在Vue的原始碼中,出場率最多的應該就數Object物件的使用上了。可以這麼說,不懂Object都沒法往下看程式碼。以下是原始碼中用到的比較多的。

  • Object.defineProperty 直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性, 並返回這個物件。
  • Object.create 使用指定的原型物件及其屬性去建立一個新的物件。
  • Object.keys 返回一個由一個給定物件的自身可列舉屬性組成的陣列,陣列中屬性名的排列順序和使用 for…in 迴圈遍歷該物件時返回的順序一致 (兩者的主要區別是 一個 for-in 迴圈還會列舉其原型鏈上的屬性)。
  • Object.freeze 凍結一個物件,凍結指的是不能向這個物件新增新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該物件已有屬性的可列舉性、可配置性、可寫性。也就是說,這個物件永遠是不可變的。該方法返回被凍結的物件。

那麼,Vue中哪個是Object呢?我們繼續試驗:

console.log(typeof Vue)
console.log(typeof new Vue())

輸出結果

function
object

結果顯示,使用new來建立的Vue例項就是個物件,所以一切對Object的操作行為都是針對Vue例項物件的。

理解setter和getter

在網上看Vue的評論是經常會聽到說

Vue無非就是setter和getter方法的運用而已

這讓我等新手一臉懵逼,這裡我們就來認識認識setter和getter。
當我們在獲取一個Vue例項data中的某個物件,如果你用console打印出來會發現,物件屬性中除了常規的物件屬性和proto物件之外還會多一個set和一個get方法,所謂的setter和getter就是它們。Vue給每一個物件屬性都添加了Observer觀察資料的獲取和修改。好吧,貼程式碼一睹真容。

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  // Object.getOwnPropertyDescriptor() 方法返回指定物件上一個自有屬性對應的屬性描述符。
  //(自有屬性指的是直接賦予該物件的屬性,不需要從原型鏈上進行查詢的屬性)
  // 物件、屬性名稱、描述~
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 當且僅當該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,同時該屬性也能從對應的物件上被刪除。預設為 false。
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) { // Watcher
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter(); // 自定義setter
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

程式碼太長?懵逼了?沒關係,我們自己來造一個簡單的setter和getter來了解一下。
其實用的就是Object.defineProperty方法中就有set和get。

  • get 一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。該方法返回值被用作屬性值。預設為 undefined。
  • set 一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。該方法將接受唯一引數,並將該引數的新值分配給該屬性。預設為 undefined。

好了,寫程式碼~

    var obj = {}
    var mValue = "abc"
    Object.defineProperty(obj, "_name", {
        configurable: false, 
        enumerable: false, 
        get: function reactiveGetter () {
            return mValue
        },
        set: function reactiveSetter (val) {
            mValue = val
        }
    })

    obj._name = "rose"

    console.log(obj)

到這裡我們列印log,物件屬性的set和get方法就出現了。
列印結果

邏輯運算子的使用

在原始碼中有很多邏輯運算子的使用,有些運用的很巧妙。這裡也科普下吧~

首先知道下可以轉換成false的值,如下:
* null
* NaN
* 0
* 空字串(”“)
* undefined

用法一:判斷條件返回true或者false

這是最基本的用法。

if (a & a.master & a.master.name) {} // 如果這三個屬性都為true值,執行if邏輯
if (a || b) {} // 如果a或者b為true值,執行if邏輯。

用法二:判斷並返回條件物件

  • && 如果幾個條件都為true,則返回最後一個條件。
  • || 幾個條件從前往後逐一判斷,如果那個條件為true,返回該條件,否則返回最後一個條件。
var getter = property && property.get;  // 如果兩個屬性都存在,將property.get賦值給getter
e && e.__ob__ && e.__ob__.dep.depend(); // 如果三個屬性都存在,執行第三條語句的方法
var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; 
// 給res賦值,如果assets[id]為true,則將其傳res;
// 如果assets[camelizedId]為true,將其傳給res;
// 如果前兩者都為false,將assets[PascalCaseId]傳給res。
var strat = strats[key] || defaultStrat;

用法三:使用兩個非

兩個感嘆號會確保引數為非值時只能為false,不會是0、空字串、undefined等非值。

if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }

未完待續

還在學習Vue原始碼中……看了很多文章、也看了一遍原始碼。內容太多,千頭萬緒,容我理清之後,用自己的文字把Vue的原始碼學習記錄分享出來。一些值得記錄下來的知識點和心得會繼續在本文中更新。
最後,想學習Vue原始碼的同學可以去買《Vue.js權威指南》這本書,雖然許多章節內容和官網是重複的,不過原始碼解析部分值得一看。我也正配合著這本書和原始碼在學習Vue。