Vue源碼學(xué)習(xí)筆記

最近偷懶好久沒有寫博客了,一直想繼續(xù)Vue學(xué)習(xí)系列,想深入Vue源碼來寫。結(jié)果發(fā)現(xiàn)自己層次不夠,對js的理解差好多。所以一直想寫一直擱置著。最近重新振作決心看完Vue源碼,并且以我們這類前端小白的角度來一步步弄懂Vue源碼。

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

Vue源碼的本質(zhì)是什么

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

var vm = new Vue({})

話不多說,老規(guī)矩用代碼說話!
讓我們來對Vue進(jìn)行打?。?/p>

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

console.log(Vue)
console.log(vm)

打印結(jié)果如圖:


vue

這里可以看到Vue$3這個方法,就是這個方法對Vue對象的構(gòu)造函數(shù)了。其實這很簡單,我們自己都可以造出一個Vue$4

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

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

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

顯示結(jié)果如圖


vue$4

綜上所述,Vue對象的本質(zhì)就是一個function,與我們的Vue$4的不同之處只在于邏輯的多與少。

必須理解Object對象

在Vue的源碼中,出場率最多的應(yīng)該就數(shù)Object對象的使用上了??梢赃@么說,不懂Object都沒法往下看代碼。以下是源碼中用到的比較多的。

  • Object.defineProperty 直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性, 并返回這個對象。
  • Object.create 使用指定的原型對象及其屬性去創(chuàng)建一個新的對象。
  • Object.keys 返回一個由一個給定對象的自身可枚舉屬性組成的數(shù)組,數(shù)組中屬性名的排列順序和使用 for...in 循環(huán)遍歷該對象時返回的順序一致 (兩者的主要區(qū)別是 一個 for-in 循環(huán)還會枚舉其原型鏈上的屬性)。
  • Object.prototype Object的原型對象。
  • Object.freeze 凍結(jié)一個對象,凍結(jié)指的是不能向這個對象添加新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該對象已有屬性的可枚舉性、可配置性、可寫性。也就是說,這個對象永遠(yuǎn)是不可變的。該方法返回被凍結(jié)的對象。

那么,Vue中哪個是Object呢?我們繼續(xù)試驗:

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

輸出結(jié)果

function
object

結(jié)果顯示,使用new來創(chuàng)建的Vue實例就是個對象,所以一切對Object的操作行為都是針對Vue實例對象的。

理解setter和getter

在網(wǎng)上看Vue的評論是經(jīng)常會聽到說

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

這讓我等新手一臉懵逼,這里我們就來認(rèn)識認(rèn)識setter和getter。
當(dāng)我們在獲取一個Vue實例data中的某個對象,如果你用console打印出來會發(fā)現(xiàn),對象屬性中除了常規(guī)的對象屬性和proto對象之外還會多一個set和一個get方法,所謂的setter和getter就是它們。Vue給每一個對象屬性都添加了Observer觀察數(shù)據(jù)的獲取和修改。好吧,貼代碼一睹真容。

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

  // Object.getOwnPropertyDescriptor() 方法返回指定對象上一個自有屬性對應(yīng)的屬性描述符。
  //(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進(jìn)行查找的屬性)
  // 對象、屬性名稱、描述~
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,同時該屬性也能從對應(yīng)的對象上被刪除。默認(rèn)為 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();
    }
  });
}

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

  • get 一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。該方法返回值被用作屬性值。默認(rèn)為 undefined。
  • set 一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。該方法將接受唯一參數(shù),并將該參數(shù)的新值分配給該屬性。默認(rèn)為 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方法就出現(xiàn)了。


打印結(jié)果

邏輯運算符的使用

在源碼中有很多邏輯運算符的使用,有些運用的很巧妙。這里也科普下吧~

首先知道下可以轉(zhuǎn)換成false的值,如下:

  • null
  • NaN
  • 0
  • 空字符串("")
  • undefined

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

這是最基本的用法。

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

用法二:判斷并返回條件對象

  • && 如果幾個條件都為true,則返回最后一個條件。
  • || 幾個條件從前往后逐一判斷,如果那個條件為true,返回該條件,否則返回最后一個條件。
var getter = property && property.get;  // 如果兩個屬性都存在,將property.get賦值給getter
e && e.__ob__ && e.__ob__.dep.depend(); // 如果三個屬性都存在,執(zhí)行第三條語句的方法
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;

然后,我對各種情況做了試驗得出以下結(jié)論:

  • && 判斷中,判斷值都為 true,返回最后一個判斷值;判斷值中有 false
    值,返回第一個 false 值。
  • || 判斷中,判斷值都為 true,返回第一個判斷值;判斷值中有 true 值也有 false 值,返回第一個為 true 的判斷值;如果判斷值都為 false,返回最后面的 false 值。

注意:這里所說的返回 false 值不一定是 Boolean 類型的 false,也可能是 0、null 等非值,詳見上文。

用法三:使用兩個非

兩個感嘆號會確保參數(shù)為非值時只能為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;
  }

數(shù)組操作

復(fù)制

先看一段代碼。

  var a = [ 'jack', 'rose', 'wade' ]
  var b = a
  b[1] = 'marry'
  console.log(a)
  console.log(b)

最后的結(jié)果是a和b的數(shù)組第二個值都變成了marry,原因就是b并不是獲得了數(shù)組內(nèi)容,而只是指向了a,a和b其實是一回事。如果我們需要復(fù)制,可以這么寫:

  var b = a.slice()

查閱MDN可知:

slice() 方法返回一個從開始到結(jié)束(不包括結(jié)束)選擇的數(shù)組的一部分淺拷貝到一個新數(shù)組對象,原始數(shù)組不會被修改。

清空

想要將非空數(shù)組的內(nèi)容清空,最快捷的方式如下:

arr.length = 0

合并

直接引用MDN的例子。

concat() 方法用于合并兩個或多個數(shù)組。此方法不會更改現(xiàn)有數(shù)組,而是返回一個新數(shù)組。

var arr1 = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];

var arr3 = arr1.concat(arr2);

// arr3 is a new array [ "a", "b", "c", "d", "e", "f" ]

遍歷

呃,使用forEach方式來遍歷數(shù)組是我剛知道的事 - -(之前都是用的for方法……),順便記錄下。

arr.forEach(function(obj){
    // 數(shù)組循環(huán)
})

幾種特殊的寫法

在代碼中有些代碼片段有些看不懂,于是在SF上進(jìn)行了提問:看Vue源碼,有兩段代碼寫法不知是何意思,求指教~。感謝大家的幫助,這里總結(jié)下。
第一段是一段在{}里的代碼。

{
    dataDef.set = function (newData) {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      );
    };
    propsDef.set = function () {
      warn("$props is readonly.", this);
    };
}

這段代碼,有的朋友說是塊級作用域、隔離作用域。不過另一種說法更可信。那就是我所看到的vue是編譯完的的代碼。源代碼其實在大括號前是有條件的。

if (process.env.NODE_ENV !== 'production') {
  ...

再來看下面的代碼

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.Vue = factory());
}(this, (function () { 'use strict';

})))

這段代碼是Vue開頭的一段代碼,它有兩個知識點。

  • 立即執(zhí)行函數(shù) —— 定義函數(shù)并立即執(zhí)行,寫法有 (function(){})() 或者 (function(){}()) 的形式。
  • 由于過去前端沒有模塊系統(tǒng),使用script標(biāo)簽引入的js腳本共享同一個作用域,如果不把代碼包起來,很容易產(chǎn)生作用域污染、變量沖突的問題。

PS:這兩個問題真是網(wǎng)友的結(jié)論,不一定完善。有空我會查資料證實~

其他知識點

  • Arguments —— 傳給函數(shù)的參數(shù)數(shù)組
  • new運算符 —— 通過構(gòu)造函數(shù)創(chuàng)建對象
  • "development" !== 'production’的作用 —— webpack打包判斷執(zhí)行環(huán)境是不是生產(chǎn)環(huán)境,如果是生產(chǎn)環(huán)境會壓縮并且沒有提示警告之類的東西
  • instanceof —— 驗證實例對象是否為該構(gòu)造函數(shù)new出來的。
  • in關(guān)鍵字 —— 判斷某個值是否在數(shù)組或?qū)ο笾小?/li>
  • Proxy對象 —— 創(chuàng)建某個對象,并定義一些行為給該對象。
  • 字符串的 charAt 是獲取第幾個字符,而 slice 方法是截取某段字符。
  • delete關(guān)鍵字 —— 用于刪除對象中的某個關(guān)鍵字。
  • call()方法 —— 函數(shù)的調(diào)用,第一個參數(shù)為this,之后為函數(shù)定義參數(shù)。試驗了下 fun(a, b)fun.call(this, a, b) 兩種寫法效果一致。另外還可以看下 apply()方法

PS:都是從MDN中找到的資料,多查MDN讓我受益良多。

未完待續(xù)

還在學(xué)習(xí)Vue源碼中……看了很多文章、也看了一遍源碼。內(nèi)容太多,千頭萬緒,容我理清之后,用自己的文字把Vue的源碼學(xué)習(xí)記錄分享出來。一些值得記錄下來的知識點和心得會繼續(xù)在本文中更新。
最后,想學(xué)習(xí)Vue源碼的同學(xué)可以去買《Vue.js權(quán)威指南》這本書,雖然許多章節(jié)內(nèi)容和官網(wǎng)是重復(fù)的,不過源碼解析部分值得一看。我也正配合著這本書和源碼在學(xué)習(xí)Vue。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容