Zepto核心模塊源代碼分析

1、Zepto核心模塊架構

Zepto核心模塊架構圖

該圖展現了Zepto核心模塊架構代碼的組織方式。主要分爲私有變量、函數和暴露給用戶的全部api。javascript

Zepto核心模塊架構代碼

該圖展現了Zepto的核心模塊架構代碼,忽略了全部實現的細節。css

var Zepto = (function() { // 私有變量($和zepto不是私有變量,它們會被暴露出去) var undefined, emptyArray = [], filter = emptyArray.filter, slice = emptyArray.slice, $, zepto = {}; // 私有函數 function likeArray() {} // Z類 function Z() {} // 構建Z對象的主要函數 zepto.matches = function() {}; zepto.fragment = function() {}; zepto.Z = function() { return new Z(dom, selector) }; zepto.isZ = function() { return object instanceof zepto.Z }; zepto.init = function() {}; zepto.qsa = function() {}; // Z對象的共享方法 $.fn = { constructor: zepto.Z, length: 0, forEach: emptyArray.forEach, reduce: emptyArray.reduce, push: emptyArray.push, sort: emptyArray.sort, splice: emptyArray.splice, indexOf: emptyArray.indexOf, concat: function() {} } // 靜態方法 $.extend = function() {}; // plugin compatibility $.uuid = 0 $.support = {} $.expr = {} $.noop = function() {} // 修改zepto.Z和Z的原型都指向$.fn zepto.Z.prototype = Z.prototype = $.fn // 把內部的一些API函數經過$.zepto命名空間暴露出去 zepto.uniq = uniq zepto.deserializeValue = deserializeValue $.zepto = zepto return $ })() window.Zepto = Zepto window.$ === undefined && (window.$ = Zepto)

Zepto核心模塊架構說明

經過上面兩張圖,咱們能夠發現Zepto核心模塊整個架構是很是的簡單整潔。首先Zepto對象經過執行一個當即調用函數進行賦值,所以Zepto對象的定義就在這個當即調用函數裏面,接下來把Zepto賦值給window:window.Zepto = Zepto。而後進行$變量名的衝突處理,若是$全局變量尚未定義,就將Zepto對象賦值給$全局變量:window.$ === undefined && (window.$ = Zepto)html

接下來分析一下當即調用函數裏面的代碼。一開始定義了一些私有變量和私有函數,這些變量和函數爲其餘代碼服務,不會暴露給用戶($zepto變量例外,zepto會經過$屬性的形式暴露出去,即等於$.zepto),其中最重要的一個函數爲Z類,調用$函數生成的對象都是Z類的實例,我稱其爲Z對象。而後就是$.zepto對象的一些方法,主要分爲兩類,第一類:fragmentZisZinitqsa,這些方法是生成Z對象的主要方法,用戶能夠經過重寫這些方法,來實現兼容低版本的瀏覽器。$.zepto對象的第二類方法:matchesuniqdeserializeValue,這些方法其實就是一些靜態方法。接下來就是Z類的原型對象:$.fn,全部Z對象的共享方法都保存在這裏。其實到這裏$.fn和Z類一點關係都沒有,可是在當即調用函數的最下面有這麼一行代碼:zepto.Z.prototype = Z.prototype = $.fn,經過將Z.prototype指向$.fn對象就可使$.fn成爲真正Z類的原型對象。再下來就是一些靜態方法$.extend和插件相關的代碼。最後返回$並賦值給Zepto。java

Zepto核心模塊架構細節

在Zepto架構圖中,我把lengthforEachreducepushsortspliceindexOf這幾個屬性放到了一齊,這是由於它們有特別的用途。當瀏覽器檢測到一個對象有以上屬性時,就把它們看成是類數組,從而當你在瀏覽器的控制檯打印它們時,它們將會以數組的形式展示出來,而不是以對象的形式。實際上,在chrome瀏覽器中,只要有length和splice屬性,對象就會被當成是類數組:git

你們也可能留意到,我上面說過生成Z對象的是Z類,它是一個私有函數,可是爲何$.fn.constructor要指向$.zepto.Z?還有就是$.zepto.Z的原型也要修改成$.fn?這實際上是由於$.zepto.isZ方法會經過$.zpeto.Z來判斷是否Z對象,而非Z函數:object instanceof zepto.Z,因此經過使Z函數和zepto.Z的函數的原型保持一致,就能夠經過zepto.Z來判斷是否Z對象了。可是爲何不直接經過Z函數來判斷是否Z對象呢:object instanceof Z?緣由在於用zepto.Z方法能夠被用戶改寫,成爲新的構造函數。因此若是一旦用戶改寫了$.zepto.Z方法以後,而zepto.isZ仍是經過Z類來判斷Z對象,那就會致使永遠都返回false了。github

2、獲取Z對象

獲取Z對象的流程圖

獲取Z對象的流程說明

其實$函數裏面什麼也沒幹,只是單純的調用$.zepto.init方法:return zepto.init(selector, context)。因此$.zepto.init方法裏面的邏輯就是獲取Z對象的邏輯。在$.zepto.init方法內部經過不一樣的參數執行不一樣的流程,最後返回一個Z對象或者在DOMContentLoaded事件觸發後執行傳進來的回調函數。具體會分爲如下幾種狀況:ajax

  • 有傳入context,回調自身:$(context).find(selector)
  • selector參數爲空,直接調用$.zepto.Z方法獲取Z對象:zepto.Z()
  • selector參數爲html片斷,調用$.zepto.fragment方法獲取對應DOM節點再調用$.zeptoZ方法獲取Z對象
  • selector參數爲css選擇器,調用$.zepto.qsa方法獲取對應DOM節點再調用$.zepto.Z方法獲取Z對象
  • selector參數爲DOM節點數組,去掉數組中值爲null的項,調用$.zepto.Z方法獲取Z對象
  • selector參數爲單個DOM節點,dom = [selector],而後調用$.zepto.Z方法獲取Z對象
  • selector參數爲Z對象,直接返回該Z對象
  • selector參數爲函數,執行$(document).ready(selector),在DOM加載完的時候調用該函數

此外上面說起到的調用$.zepto.Z方法只是簡單的new一個Z對象:return new Z(dom, selector),Z類裏面會把dom和selector保存起來,供其餘方法使用。Z類的代碼也很簡單:正則表達式

function Z(dom, selector) { var i, len = dom ? dom.length : 0 for (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || '' }

$.zepto.qsa方法說明

$.zepto.qsa方法是Zepto的css選擇器實現,很是重要,可是從上圖能夠看出代碼實現不復雜,主要就是利用原生的element.querySelectorAll方法實現。只不過對selector參數添加了一些判斷,對簡單的id、class和標籤名使用效率更好的原生方法實現。chrome

$.zepto.fragment方法說明

$.zepto.fragment方法的主要功能是根據給出的html片斷生成對應的DOM節點。實現的大概思路是生成一個臨時的父元素,而後把html片斷參數賦值給父元素的innerHTML,這樣就能夠生成html片斷對應的節點。其中須要注意的是臨時的父元素在大部分狀況之下應該是一個div節點,可是當html片斷是表格相關的元素時,父元素就不能是div節點。例如html片斷參數本該生成的是tr節點,因此它的臨時父元素就應該是tbody節點。這是由於若是表格元素沒有添加到正確的父元素裏面,會致使不能生成正確的節點。api

3、經常使用共享函數解釋

$.fn.ready

Zepto的經常使用功能之一就是在$函數裏面傳入一個回調函數,把代碼都寫在該回調函數裏面,保證代碼在DOM加載完以後才執行,例如:$(function(){})。其實該方法是調用$.fn.ready完成的,前面的代碼等價於:$(document).ready(function(){})

該方法先根據document.readyState的值判斷DOM是否加載完成,完成就直接觸發傳進來的回調函數。不然在DOMContentLoaded事件觸發的時候調用該回調函數。

$.fn.show

$.fn.show方法也是咱們用得很是多的一個方法,實現邏輯爲:一、內聯樣式element.style.display值爲'none'的話就設置爲空字符串''。二、若是是在非內聯樣式設置了display:none,那就先獲取對應元素的默認display值,賦值給element.style.display。而獲取元素的默認值就經過新建一個同名標籤節點,而後獲取它的display值就等於該元素的默認display值了。此外,獲取默認值涉及到DOM的操做,速度比較慢,因此每次獲取到的值都會保存到私有變量elementDisplay中緩存起來,之後要獲取同類標籤默認值的時候就能夠在緩存裏面獲取。

$.fn.addClass

$.fn.addClass的實現思路大概是:一、判斷元素是否有對應類名。二、若是沒有的話就直接添加到元素的className中。

須要展開的就是如何判斷是否有對應類型,Zepto的作法就是經過類名建立能夠正則表達式來進行判斷,建立正則表達式的函數以下:

funcion classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) }

4、鏈式調用

使用Zepto過程當中最爲經常使用的特性應該就是鏈式調用了,例如:$('.outer-div').find('.inner-div').addClass('div')。這種功能其實很容易實現,具體方法就是在$.fn中的共享方法裏面返回自身this就能夠了。下面爲一個簡單的例子:

$.fn.sayHi = function() { alert('hi'); return this; }

5、功能擴展

Zepto按功能劃分把代碼分散到不一樣的js文件裏,默認下載的話包含zepto、event、ajax、form 和 ie這幾個模塊,其中zepto就是本文所介紹的核心模塊。此外Zepto還提供fx、deferred、touch等可選模塊,你們能夠根據本身的需求構建本身所須要的Zepto,具體方法能夠看官網的Github

而合併各模塊的方法也很簡單,只要給$對象和$.fn對象添加方法就完成了靜態方法和共享方法的擴展,例子以下:

;(function($){ $.Sayhi = function(){}; // 擴展靜態方法 $.fn.sayhi = function(){}; // 擴展共享方法 })(Zepto)

6、結束語

Zepto核心模塊的分析到此結束,具體的實現細節你們能夠到個人Github查看我作的詳細註釋。Zepto核心代碼量只有1000行左右,仍是很值得你們花點時間去研究一下的。接下來我也會分析每個模塊,而且分享出來。若是你們發現了這篇文章的錯漏之處,能夠在下面作出留言,我會盡快回復 :-D

相關文章
相關標籤/搜索