關於 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.Z
和Z
的原型。也就是說後兩者的原型就是$.fn
。
結合圖例仔細看幾遍,$
, zepto
, Z
, zepto.Z
, zepto.init
, $.fn
之間的關係應該就清晰多了。
至於圖中出現的 zepto.qsa
和 zepto.fragment
,那是在 zepto.init
方法中被呼叫的重要方法,所以一併也畫進去了,對整體結構沒有影響。
執行詳細過程
雖然知道了原始碼內部三個主要物件的關係,但是原始碼又是如何起作用的呢?
我們知道,Zepto 使用時都是以選擇器為開端的。當我們使用 $()
來生成一個 Zepto 物件時,內部究竟發生了什麼?現在我們就從原始碼入手開始分析。
- 進入
$
方法,內部呼叫zepto.init
方法。 - 進入
zepto.init
方法。該方法判斷傳入的selector
引數的型別,對其做相應的處理。 - 如果是 html 字串,就呼叫
zepto.fragment
方法。 - 如果是選擇器字串,就呼叫
zepto.qsa
方法。 - 當
selector
引數是其他型別時(物件、陣列、Zepto 例項、函式······),做其他的相應處理。 - 最終呼叫
zepto.Z(dom, selector)
對生成的 dom 做一層包裹。 - 進入
zepto.Z
方法。該方法通過new Z(dom, selector)
來生成一個Z
的例項並返回。 - 進入建構函式
Z
。Z
的內部很簡單,僅僅是對傳入的 dom 做一個簡單的迴圈拷貝,並複製了它的length
,生成了一個類陣列物件。這個物件就是一開始$()
方法最終返回的物件。
至此,經過層層遞進的分析,我們得到了結論:$()
返回的就是一個 Z
的例項。
前面程式碼的倒數第三行有 zepto.Z.prototype = Z.prototype = $.fn
。這行程式碼非常重要,它讓 Z
的 prototype
指向了 $.fn
,這也是為什麼 Z
的例項可以呼叫 $.fn
中的一大堆方法。
至於這個等式的前半部分,zepto.Z.prototype = Z.prototype
,大概是由於 $.fn
的 constructor
被指給了zepto.Z
。為了讓 $() instanceof Zepto.zepto.Z
成立,所以才有這一等式。
不過,我認為這多少有點違揹人的直覺,也許讓 $.fn
的 constructor
指向 $
本身更好。畢竟在 jQuery 中,$() instanceof jQuery === true
,但是在 Zepto 中,$() instanceof Zepto === false
。
至於這個設計的優劣,又是另一個話題了,大家可以討論。