Zepto is a minimalist JavaScript library for modern browsers with a largely jQuery-compatible API. If you use jQuery, you already know how to use Zepto.
Zepto 是一個用於現代瀏覽器的與 jQuery 大致兼容的 JavaScript 庫。若是你使用過 jQuery 的話,你已經知道如何使用 Zepto 了。css
Zepto.js 官方網對其進行的描述簡而言之即爲一個大致兼容 jQuery 的 JavaScript 庫,所以是否選擇分析 Zepto 這樣的庫前置問題即爲,對於此情此景下的前端入門者(2017 年的前端入行者,Like Me)是否還有分析 jQuery 源碼的必要?html
我心中的答案,即是有也沒有,先說沒有必要的一方面:前端
var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 },
if ( support.qsa && !nonnativeSelectorCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) && // Support: IE 8 only // Exclude object elements (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") )
-再說有必要的一方面,仍然拿 querySelectorAll
舉例,Zepto 這一 jQuery 兼容庫中將其封裝爲了以下的 qsa
函數:node
zepto.qsa = function(element, selector) { //\ ... return element.getElementById && isSimple && maybeID ? (found = element.getElementById(nameOnly)) //\ ... : slice.call( isSimple && !maybeID && element.getElementsByClassName ? maybeClass ? element.getElementsByClassName(nameOnly) : element.getElementsByTagName(selector) : element.querySelectorAll(selector) ); };
即在調用該函數時,根據類型先作出判斷,若是目標爲可識別的類型,那麼採起相應的方法進行選擇,最終降級到使用 querySelectorAll()
這一函數進行選擇。採用該方法的目的即爲了儘量的提升選擇器性能,參考 jsperf getElementById vs querySelector 的運行結果即可略知一二:webpack
測試調用 | Ops / Sec | Ops 差距 |
---|---|---|
document.getElementById("foo") | 27,869,670 | fastest |
document.querySelector("#foo") | 11,206,845 | 62% slower |
document.querySelector("[id=foo]") | 11,281,576 | 59% slower |
另外一個例子,關於 jQuery 對象本質的問題,以簡化的 Zepto 爲例,其擁有以下多種的初始化方式:git
$() $(selector, [context]) ⇒ collection $(<Zepto collection>) ⇒ same collection $(<DOM nodes>) ⇒ collection $(htmlString) ⇒ collection $(htmlString, attributes) ⇒ collection v1.0+ Zepto(function($){ ... })
而其調用生成的對象僅僅形似一個數組,但卻如何實現能夠簡單的操做 DOM 元素:github
// 構造一個空的 Zepto(Z) 對象 > $() [selector: ""] -> selector: "" -> length: 0 -> __proto__: Object // 選擇並着色 > $("p:last-child").css('background-color', 'aliceblue')
答案便存在於 Zepto 的模塊結構當中,$.fn
包含暴露在外的工具函數,當一個 Z 對象建立時將其設定爲原型沒,即可獲取其中的工具函數:web
zepto.Z.prototype = Z.prototype = $.fn;
這樣設計思想與實現方式的例子在 jQuery/Zepto 中比比皆是。面對有仍是沒有必要閱讀 jQuery 源碼的問題,比起 jQuery 中寫滿瀏覽器崢嶸歷史的長篇鉅著,Zepto 將其簡化爲了:ajax
最大限度的下降了閱讀成本,所以該問題的答案,能夠回答爲去粗取精,經過分析一種 jQuery 20% 代碼量的最簡核心實現(Zepto),領略其 80% 的設計思想。shell
Zepto 分析環境的搭建僅須要一個常規頁面和原始代碼:
熱身部分,先從 Zepto 的模塊結構開始:
Zepto 核心部分包含 zepto module
等五個默認編譯入生產代碼的模塊,而且給出了擴展插件的方法,那麼第一個問題即是,Zepto 是如何引入並提供模塊結構的:
package.json
發現 Zepto 項目提供了三個公用的命令行入口,供 coffee-script
使用,實際入口爲 make
:"scripts": { "test": "coffee make test", "dist": "coffee make dist", "start": "coffee test/server.coffee" },
make
文件,直接找到它生成 dist
的過程://\ Line 33 target.dist = -> target.build() target.minify() target.compress() target.build = -> cd __dirname mkdir '-p', 'dist' //\ 這裏標明瞭 5 個默認的編譯模塊,能夠經過環境變量改變編譯目標,且這些模塊名稱即爲對應的文件名 modules = (env['MODULES'] || 'zepto event ajax form ie').split(' ') module_files = ( "src/#{module}.js" for module in modules ) //\ 聲明許可證,放在 dist 頭部,將目標源碼文件中的註釋刪除,將 3 行以上的空行轉換爲兩個空行寫入 intro = "/* Zepto #{describe_version()} - #{modules.join(' ')} - zeptojs.com/license */\n" dist = cat(module_files).replace(/^\/[\/*].*$/mg, '').replace(/\n{3,}/g, "\n\n") //\ 判斷是否將 AMD Layout 寫入,若是是,則將上文代碼填入 AMD 代碼段中,回報體積 dist = cat('src/amd_layout.js').replace(/YIELD/, -> dist.trim()) unless env['NOAMD'] (intro + dist).to(zepto_js) report_size(zepto_js)
幾點額外的補充:
make
中的 #!/usr/bin/env coffee
是類 Unix 操做系統中表示文本文件解析器的聲明 Shebang),相似於 HTML 文檔的 DOCTYPE
,該文件寫法可明顯看出 Linux Shell 腳本的意味,不過採用了 shelljs
這一運行在 Nodejs 上的庫進行了跨平臺。make
中的 compress
過程當中用到了 gzip
進行壓縮: inp.pipe(gzip).pipe(out)
,該項壓縮對於用戶是透明的,用戶瀏覽器能夠經過 Content-Encoding
HTTP 字段獲知該文件已被壓縮過並提供預解壓操做,詳見 MDN - HTTP 協議中的數據壓縮。make
腳本中還有不少的借鑑之處,例如 102 行 git
相關操做,以及 108 行開始調用 uglify.js
進行代碼壓縮的過程等,不管用 grunt
或 webpack
組織流水線也只是類似的工序,提供多個出口。src/amd_layout
這一沒被列入模塊列表中的文件,其做用即爲兼容 AMD 標準://\ src/amd_layout //\ 做用於全局的 IIFE (function(global, factory) { //\ AMD 兼容寫法 if (typeof define === 'function' && define.amd) define(function() { return factory(global) }) else factory(global) }(window, function(window) { //\ 若是包含 AMD 編譯,就將上文代碼完整寫入該函數 YIELD return Zepto }))
此模塊忽略 AMD 相關邏輯會獲得一個 JavaScript 庫中常見的當即執行表達式(IIFE)結構,使用該結構的目的即爲構造塊級做用域,防止庫中的變量對全局進行污染,是一種很是經常使用的包裝方法,詳見 MDN - IIFE:
(function(global, factory) { //\ ... }( //\ ... ))
分析完 Zepto 模塊結構,開始進入 Zepto 主模塊,主模塊框架以下:
//\ src/zepto.js //\ 將 Zepto 定義爲一個 IIFE var Zepto = (function() { //\... Zepto 內部變量定義部分 6-47 行 //\... Zepto 內部對象 zepto / 公共函數定義部分 48-167 行 //\... zepto/Z/$ 關係構造 zepto.Z = zepto.isZ = zepto.init = $ = extend = $.extend = //\... 與 DOM 的橋樑 - querySelectorAll 實現 zepto.qsa = //\ $ 對象對外的工具函數 279-404 行 //\ 定義 $.fn,經過原型鏈賦予方法 $.fn = { constructor: zepto.Z, //\ ... } //\ 一些對於 $.fn 的其餘函數實現 852-936 行 //\ 繼續處理 zepto/Z/$ 的關係,將 Z 的原型指向 $.fn zepto.Z.prototype = Z.prototype = $.fn zepto.uniq = uniq zepto.deserializeValue = deserializeValue //\ 將內置對象 zepto 掛載到 $ 對象 $.zepto = zepto //\ 將 $ 做爲 IIFE 返回值返回 return $ })() //\ 將 window.Zepto 指向 Zepto IIFE 的返回值 '$' window.Zepto = Zepto //\ 若是 '$' 還未被佔用,就將其也初始爲 Zepto IIFE 的返回值 '$' window.$ === undefined && (window.$ = Zepto)
其核心設計思想即體如今 zepto/Z/$
這三個組件之間的關係上,處理他們的關係也正是本篇的目的所在。