摘要:選擇的理由是一個用于現代瀏覽器的與大體兼容的庫。環境搭建分析環境的搭建僅需要一個常規頁面和原始代碼一個常規頁面打開的首頁即可,在開發人員工具中即可使用原始代碼本篇分析的代碼參照,進入該代碼分支中即可。
選擇 Zepto 的理由
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 了。
Zepto.js 官方網對其進行的描述簡而言之即為一個大體兼容 jQuery 的 JavaScript 庫,因此是否選擇分析 Zepto 這樣的庫前置問題即為,對于此情此景下的前端入門者(2017 年的前端入行者,Like Me)是否還有分析 jQuery 源碼的必要?
我心中的答案,便是有也沒有,先說沒有必要的一方面:
jQuery 包含大量針對“兩套標準”的兼容實現,最明顯的例子即是對 XHR 的處理方式上,因此對于類似的片段,尋找解決方案已經沒有太大的意義:
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 },
jQuery 處理邏輯的基本對象基于 DOM,因此同時包含基礎庫 Sizzle.js 用以實現純粹的跨平臺選擇器實現,但由于 Document.querySelectorAll() 的兼容實現已經覆蓋所有現代瀏覽器,類似這樣的判斷也沒有了太多的借鑒價值:
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 函數:
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 的運行結果便可略知一二:
測試調用 | 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 為例,其擁有如下多種的初始化方式:
$() $(selector, [context]) ? collection $() ? same collection $( ) ? collection $(htmlString) ? collection $(htmlString, attributes) ? collection v1.0+ Zepto(function($){ ... })
而其調用生成的對象僅僅形似一個數組,但卻如何實現可以簡單的操作 DOM 元素:
// 構造一個空的 Zepto(Z) 對象 > $() [selector: ""] -> selector: "" -> length: 0 -> __proto__: Object // 選擇并著色 > $("p:last-child").css("background-color", "aliceblue")
答案便存在于 Zepto 的模塊結構當中,$.fn 包含暴露在外的工具函數,當一個 Z 對象創建時將其設定為原型沒,便可獲取其中的工具函數:
zepto.Z.prototype = Z.prototype = $.fn;
這樣設計思想與實現方式的例子在 jQuery/Zepto 中比比皆是。面對有還是沒有必要閱讀 jQuery 源碼的問題,比起 jQuery 中寫滿瀏覽器崢嶸歷史的長篇巨著,Zepto 將其簡化為了:
主體部分 1000 行的代碼規模
高度的 jQuery API 兼容
模塊化的組合方式,默認編譯方式只包含現代瀏覽器支持
最大限度的降低了閱讀成本,因此該問題的答案,可以回答為去粗取精,通過分析一種 jQuery 20% 代碼量的最簡核心實現(Zepto),領略其 80% 的設計思想。
環境搭建Zepto 分析環境的搭建僅需要一個常規頁面和原始代碼:
一個常規頁面:打開 Zepto 的首頁即可,在開發人員工具中即可使用 Zepto
原始代碼:本篇分析的 Zepto 代碼參照 Commit 3172e9,進入該代碼分支中即可。
熱身部分,先從 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 */ " dist = cat(module_files).replace(/^/[/*].*$/mg, "").replace(/ {3,}/g, " ") // 判斷是否將 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 模塊結構,開始進入 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/$ 這三個組件之間的關系上,處理他們的關系也正是本篇的目的所在。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108672.html
摘要:對象構造函數讀入的兩個參數與在中也有明確的規范,用以保證構造函數的簡單性。 承接第三篇末尾內容,本篇結合官方 API 進入對 Zepto 核心的分析,開始難度會比較大,需要重點理解幾個核心對象的關系,方能找到線索。 $() 與 Z 對象創建 Zepto Core API 的首個方法 $() 按照其官方解釋: Create a Zepto collection object by pe...
摘要:承接第一篇末尾內容,本部分開始進入主模塊,分析其設計思路與實現技巧下文代碼均進行過重格式化,但代碼版本同第一部分內容且入口函數不變的選擇器先從第一個與原型鏈構造不直接相關的工具函數說起,觀察的設計思路。 承接第一篇末尾內容,本部分開始進入 zepto 主模塊,分析其設計思路與實現技巧(下文代碼均進行過重格式化,但代碼 Commit 版本同第一部分內容且入口函數不變): Zepto 的選...
摘要:在觸發事件前,先將保存定時器的變量釋放,如果對象中存在,則觸發事件,保存的是最后觸摸的時間。如果有觸發的定時器,清除定時器即可阻止事件的觸發。其實就是清除所有相關的定時器,最后將對象設置為。進入時,立刻清除定時器的執行。 大家都知道,因為歷史原因,移動端上的點擊事件會有 300ms 左右的延遲,Zepto 的 touch 模塊解決的就是移動端點擊延遲的問題,同時也提供了滑動的 swip...
摘要:此模塊包含的設計思路即為預以匹配降級方案。沒有默認編譯該模塊,以及利用該模塊判斷后提供平臺相關邏輯的主要原因在于其設計原則的代碼完成核心的功能。此處,也引出了代碼實現的另一個基本原則面向功能標準,先功能覆蓋再優雅降級。 在進入 Zepto Core 模塊代碼之前,本節簡略列舉 Zepto 及其他開源庫中一些 Polyfill 的設計思路與實現技巧。 涉及模塊:IE/IOS 3/Dete...
摘要:返回值為,如果能查找到元素,則將元素以數組的形式返回,否則返回空數組排除不合法的。的第一個字符為,并且為標簽。如果存在,則查找下選擇器為的所有子元素。正則表達式為如果沒有指定標簽名,則獲取標簽名。包裹元素的即為所需要獲取的。 經過前面三章的鋪墊,這篇終于寫到了戲肉。在用 zepto 時,肯定離不開這個神奇的 $ 符號,這篇文章將會看看 zepto 是如何實現 $ 的。 讀Zepto源碼...
閱讀 3580·2021-11-18 13:20
閱讀 2732·2021-10-15 09:40
閱讀 1750·2021-10-11 10:58
閱讀 2119·2021-09-27 13:36
閱讀 2592·2021-09-07 10:06
閱讀 1853·2021-08-11 11:21
閱讀 1430·2019-08-29 17:04
閱讀 2085·2019-08-29 14:06