圖示 Zepto 原始碼結構

@㍿社長發表於2017-10-09

關於 Zepto 原始碼結構分析的文章已經很多了,本文主要從兩點,即圖示詳細步驟跟蹤上,對其進行分析。

首先還是上原始碼:

外層結構原始碼

var Zepto = (function() {

})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
複製程式碼

外層結構很簡單,無非是執行一個立即執行函式,把返回結構賦給 Zepto,再把 Zepto 繫結到全域性。最後,如果全域性的美元符號 $ 沒有被佔用,則把 Zepto 賦給 $。很多文章裡都已經討論過了,不再細說。

核心結構原始碼

var zepto = {}, $

function Z(doms) {
  var len = doms.length 
  for (var i = 0; i < len; i++) {
    this[i] = doms[i]
  }
  this.length = doms.length
}

zepto.fragment = function(html, name, properties) {
  // 根據 html 字串生成 dom
}

zepto.isZ = function(object) {  return object instanceof zepto.Z}
zepto.Z = function(doms) {
  return new Z(doms)
}

zepto.init = function(selector, context) {
  var dom
  // 根據 selector 生成 dom
  // ···
  return zepto.Z(dom, selector)}

$ = function(selector, context){  
  return zepto.init(selector, context)
}

zepto.qsa = function(element, selector){  
  // 根據 選擇器字串生成 dom
}

$.type = type
  ······
  // 把各種工具方法繫結到 $ 上

$.fn = {  constructor: zepto.Z,
  forEach: emptyArray.forEach  ······
  // 把各種例項方法繫結到 $.fn 上
}

zepto.Z.prototype = Z.prototype = $.fn

$.zepto = zepto

return $複製程式碼

以上程式碼是抽取主要部分後得到的骨幹,饒是如此,結構就已經很複雜了。物件和原型之間互相賦值和引用,函式之間互相呼叫,楞一遍看下來讓人非常暈。

我們現在就來整理一下。

首先,這裡有三個最主要的物件,這三個物件就是:

$, zepto, Z。注意,這裡的 zepto 首字母是小寫,不要和外層結構的 Zepto 搞混了。

下面就對這三者之間的關係進行分析。


$, zepto, Z 關係分析

仔細看原始碼,分析三者以及它們之間的關係,我們發現以下幾個事實:

  • 內部結構最終返回的是 $ ,也就是說其實就是 $ 被賦給了外層的 Zepto
  • $ 本身是一個函式,它呼叫了 zepto.init 方法 並返回。
  • zepto.init 函式做了很多操作,其中最後一步呼叫了 zepto.Z
  • Z 是一個建構函式。
  • zepto.Z 方法呼叫建構函式 Z,返回一個 Z 的例項。
  • zepto 物件被賦給了 $.zepto 屬性。
  • $.fn 被賦給了 zepto.ZZ 的原型。也就是說後兩者的原型就是 $.fn
好了,看上面這一大坨字到現在,是不是還是覺得特別暈?沒關係,一圖抵千言,我特意用腦圖工具畫了一張關係圖,請往下看。

圖示 Zepto 原始碼結構

結合圖例仔細看幾遍,$, zepto, Z, zepto.Z, zepto.init, $.fn 之間的關係應該就清晰多了。

至於圖中出現的 zepto.qsazepto.fragment,那是在 zepto.init 方法中被呼叫的重要方法,所以一併也畫進去了,對整體結構沒有影響。


執行詳細過程

雖然知道了原始碼內部三個主要物件的關係,但是原始碼又是如何起作用的呢?

我們知道,Zepto 使用時都是以選擇器為開端的。當我們使用 $()來生成一個 Zepto 物件時,內部究竟發生了什麼?現在我們就從原始碼入手開始分析。

  1. 進入 $ 方法,內部呼叫 zepto.init 方法。
  2. 進入 zepto.init 方法。該方法判斷傳入的 selector 引數的型別,對其做相應的處理。
    1. 如果是 html 字串,就呼叫 zepto.fragment 方法。
    2. 如果是選擇器字串,就呼叫 zepto.qsa 方法。
    3. selector 引數是其他型別時(物件、陣列、Zepto 例項、函式······),做其他的相應處理。
    4. 最終呼叫 zepto.Z(dom, selector) 對生成的 dom 做一層包裹。
  3. 進入 zepto.Z 方法。該方法通過 new Z(dom, selector) 來生成一個 Z 的例項並返回。
  4. 進入建構函式 ZZ 的內部很簡單,僅僅是對傳入的 dom 做一個簡單的迴圈拷貝,並複製了它的 length,生成了一個類陣列物件。這個物件就是一開始 $() 方法最終返回的物件。

至此,經過層層遞進的分析,我們得到了結論:$() 返回的就是一個 Z 的例項。

前面程式碼的倒數第三行有 zepto.Z.prototype = Z.prototype = $.fn 。這行程式碼非常重要,它讓 Zprototype 指向了 $.fn,這也是為什麼 Z 的例項可以呼叫 $.fn 中的一大堆方法。

至於這個等式的前半部分,zepto.Z.prototype = Z.prototype,大概是由於 $.fnconstructor 被指給了zepto.Z。為了讓 $() instanceof Zepto.zepto.Z 成立,所以才有這一等式。

不過,我認為這多少有點違揹人的直覺,也許讓 $.fnconstructor 指向 $ 本身更好。畢竟在 jQuery 中,$() instanceof jQuery === true ,但是在 Zepto 中,$() instanceof Zepto === false

至於這個設計的優劣,又是另一個話題了,大家可以討論。


相關文章