摘要:對象構造函數讀入的兩個參數與在中也有明確的規范,用以保證構造函數的簡單性。
承接第三篇末尾內容,本篇結合官方 API 進入對 Zepto 核心的分析,開始難度會比較大,需要重點理解幾個核心對象的關系,方能找到線索。
$() 與 Z 對象創建Zepto Core API 的首個方法 $() 按照其官方解釋:
Create a Zepto collection object by performing a CSS selector, wrapping DOM nodes, or creating elements from an HTML string.
通過使用 CSS 選擇器,或包裝 DOM 節點,或從 HTML 片段中創建一個 Zepto 集合對象。該處即為核心模塊的入口,對應 src/zepto.js 中的如下調用:
// Line 231 $ = function(selector, context) { return zepto.init(selector, context); }; // Line 186 zepto.init = function(selector, context) { // 該函數的幾種出口 /* 1 */ return zepto.Z(); /* 2 */ return $(context).find(selector); /* 3 */ return $(document).ready(selector); /* 4 */ return selector; /* 5 */ return $(context).find(selector); /* 6 */ return zepto.Z(dom, selector); };
$ 是一個收緊入口的 zepto.init 別名,這樣的函數傳遞使得 zepto.init 的具值參數最多為 2 個。進入 zepto.init 函數,先忽略中間的處理細節,注意到最終出口共有如上列舉的六種,第 1 種與第 6 種陷入一個名為 Z 的對象創建過程,下文中發現 $.fn 對象將 constructor 指向了 zepto.Z 且設定了 Z 對象的原型,此處正是 Zepto 的結構組織方式所在:
// Line 172 zepto.Z = function(dom, selector) { return new Z(dom, selector); }; // Line 408 $.fn = { constructor: zepto.Z, // ... } // Line 938 zepto.Z.prototype = Z.prototype = $.fn;
加載 Zepto 代碼后,可在瀏覽器中進行如下調試即可初步認識 Z 對象的生成:
// 創建任意一個 Zepto 對象,他的原型均指向 $.fn > $().__proto__ === $.fn true // $ 對象中包含了 Zepto 包級別的工具函數,主要用于擴展 Zepto 功能,以 $.func 方式對外暴露 > Object.keys($) (22)?["extend", "contains", "type", ... ] // $.fn 對象包含了 Zepto 對外暴露的操作 API,面向對象均為通過 zepto.Z 函數創建的 Z 對象 > Object.keys($.fn) (75)?["constructor", "length", "forEach", "reduce", ... ] // 因此 Z 對象上的函數調用指向其原型 $.fn 上的同名函數 > $().hasClass === $.fn.hasClass true
繼續調試過程,分析 Z 對象的實質:
// 創建一個空的 Z 對象,發現 Chrome 將其識別為一個“數組” > $() Z?[selector: ""] // 但其實際并不是 Array 包裝類的一個實例 > $() instanceof Array false > $() instanceof Zepto.zepto.Z true // Zepto 通過 "擴展數組" 這種方式使得其對外體現為一個對外包含成員變量 selector 的數組,對內可以進行函數式運算的靈活數據結構 > Object.keys($()) (2)?["length", "selector"]
這樣 "擴展數組" 生成的方法在于這個非常簡明的函數,這里也揭示了下文中 this 的指向為一個 Z 對象:
// Line 128 // Z 對象的 Constructor 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.init 方法,其實質即為調用該構造函數生成 Z 對象(2/3/5 出口返回的是在某個 Z 對象下操作生成的 Z 對象,4 出口實際上傳入的是 Z 對象因此直接返回自身)。Z 對象 構造函數讀入的兩個參數 dom 與 selector 在 Zepto 中也有明確的規范,用以保證 Z 構造函數的簡單性。
dom 的生成與 selector 的取值范圍dom 的生成嚴格依賴于以下的幾個正則表達式以及其生成函數 zepto.fragment,該步驟比較晦澀,簡而言之功能為映射原生 dom 結構至相應 Z 對象的關系:
// Line 10 // 用以匹配普通的 HTML 片段,這里面 Group 1 (w+|!) 用來拿標簽類型,例如: div fragmentRE = /^s*<(w+|!)[^>]*>/, // 用以匹配單標簽或兩個閉合標簽間沒有內容的情況,如:/ singleTagRE = /^<(w+)s*/?>(?:1>|)$/, // 用以匹配不應自閉合的標簽,Group 1 / Gruop 2 均為 Group 1 判斷條件約束的非自閉和標簽 tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([w:]+)[^>]*)/>/gi, // Line 140 zepto.fragment = function(html, name, properties) { var dom, nodes, container; // 如果滿足 singleTagRE 就調用 document 創建該元素,再通過 $() 初始化 if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1)); if (!dom) { // 如果聲明 replace 將 html 標簽修復 if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>"); // name 如果不指定,默認取標簽類型 if (name === undefined) name = fragmentRE.test(html) && RegExp.$1; // 如果 name 不在全局變量 containers 內,取 * if (!(name in containers)) name = "*"; // 從 containers 拿具體標簽,創建 html 內容 container = containers[name]; container.innerHTML = "" + html; // 將 container 中每個子節點的內容刪除,此時 dom 剩余部分是一個 Z 元素 // 參考:https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild dom = $.each(slice.call(container.childNodes), function() { container.removeChild(this); }); } // 如果傳入第三個參數為普通對象 if (isPlainObject(properties)) { // 根據 dom 創建 Z 對象,再將 properties 中定義的屬性掛載到 $(dom) 上 nodes = $(dom); $.each(properties, function(key, value) { if (methodAttributes.indexOf(key) > -1) nodes[key](value); else nodes.attr(key, value); }); } // 返回 dom return dom; };
分析期間的幾個疑問:
為什么有三個正則用于測試:
為了保證下三種調用方式生成的結果相同(實際上描述的也是同一個空標簽):
> $.zepto.fragment("") Z?[div, selector: ""] > $.zepto.fragment("") Z?[div, selector: ""] > $.zepto.fragment("") Z?[div, selector: ""]container 與 containers:
containers 用于當以 name 為某個標簽寫為 innerHTML 時其父元素是合理的,這里僅在是表格元素的情況下替換了父容器,其他情況下默認采用 div.
// Line 22 containers = { tr: document.createElement("tbody"), tbody: table, thead: table, tfoot: table, td: tableRow, th: tableRow, "*": document.createElement("div") },isObject 與 isPlainObject:
本函數中判定第三個參數類型時,采用了 isPlainObject 而不僅僅是 isObject 是為了避開數組等其他可能被識別為 Object 的情況,此處校驗該對象的原型。
// Line 74 function isPlainObject(obj) { return ( isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype ); }selector 較 DOM 要簡單得多,如果不引入額外的 Selector 模塊,其取值范圍就是 Document.querySelectorAll() 這一瀏覽器原生方法支持的選擇器的取值范圍。(注意 Zepto 源代碼中 selector 多用于標識形參,這里提到的 selector 實際上是 Z 對象中的 selector 屬性)
zepto.init 的六種出口有了上面的鋪墊,終于可以進入 zepto.init,解析其六種出口對應的 Z 對象創建或操作過程:
zepto.init = function(selector, context) { var dom // 出口 1: 當第一個入參為 Falsy 值,那么直接返回一個空的 Z 對象(該出口行為為創建) // 例如: $() / $(undfined) if (!selector) return zepto.Z() // 如果 selector 形參是字符串 else if (typeof selector == "string") { // 當 selector 形參第一個字符為 "<" 且符合 fragmentRE 的匹配結果,那么傳入 zepto.fragment 方法,創建 Z 對象,例如:$("") // 跳至出口 6 if (selector[0] == "<" && fragmentRE.test(selector)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null // 出口 2: 如果傳入的 context 不為空,那么基于 context 創建一個 Z 對象并返回包含 context 的 Z 對象(該出口行為為選擇) // 例如 $("p", { text:"Hello", id:"greeting", css:{color:"darkblue"} }) else if (context !== undefined) return $(context).find(selector) // 出口3:如果沒有 context 要求,則直接在全文 qsa(該出口行為為選擇) // 例如 $("p#grt") else dom = zepto.qsa(document, selector) } // 出口4:如果 selector 形參為函數,那將其掛載在 ready 方法內(該出口行為為邏輯 Defer) // 例如 $(() => alert("DONE)) else if (isFunction(selector)) return $(document).ready(selector) // 出口 5: 當第一個入參已經為 Z 對象時直接返回自身(該出口行為為提供兼容) // 例如 JSON.stringify($($("p"))) === JSON.stringify($("p")) else if (zepto.isZ(selector)) return selector // 如果前方判斷條件全部失敗,進入最終的類型判斷 else { // 如果傳入的 Selector 是數組,那么去除 Falsy 值合并,例如 $([$("div"), $("body")]) if (isArray(selector)) dom = compact(selector) // 此處為降級條件,跳入出口 6 else if (isObject(selector)) dom = [selector], selector = null // 剩余部分參考出口 2/3 處理方式 else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null else if (context !== undefined) return $(context).find(selector) else dom = zepto.qsa(document, selector) } // 出口 6,以上文處理規范的 dom / selector 創建 Z 對象(該出口行為為創建) return zepto.Z(dom, selector) }此時,也可以給出 dom 對象與 Z 對象的真正關系,dom 是用于 Z 對象生成過程中的中間 Z 對象;zepto.init 與 zepto.fragment 必須合并起來才能真正理解 $() 可以多類型進,單類型出的設計技巧及真實意圖,這也是 Zepto 的精華所在。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/108755.html
摘要:選擇的理由是一個用于現代瀏覽器的與大體兼容的庫。環境搭建分析環境的搭建僅需要一個常規頁面和原始代碼一個常規頁面打開的首頁即可,在開發人員工具中即可使用原始代碼本篇分析的代碼參照,進入該代碼分支中即可。 選擇 Zepto 的理由 Zepto is a minimalist JavaScript library for modern browsers with a largely jQue...
摘要:承接第一篇末尾內容,本部分開始進入主模塊,分析其設計思路與實現技巧下文代碼均進行過重格式化,但代碼版本同第一部分內容且入口函數不變的選擇器先從第一個與原型鏈構造不直接相關的工具函數說起,觀察的設計思路。 承接第一篇末尾內容,本部分開始進入 zepto 主模塊,分析其設計思路與實現技巧(下文代碼均進行過重格式化,但代碼 Commit 版本同第一部分內容且入口函數不變): Zepto 的選...
摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數據,另一部分是觸發事件,提交表單。最終返回的結果是一個數組,每個數組項為包含和屬性的對象。否則手動綁定事件,如果沒有阻止瀏覽器的默認事件,則在第一個表單上觸發,提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數據,另一部分是觸發 submit 事件,提交表單。 讀 Zepto 源碼系列文章已...
摘要:本來想學習一下的源碼,但由于的源碼有多行,設計相當復雜,所以決定從開始,分析一個成熟的框架的代碼結構及執行步驟。同時發表在我的博客源碼分析代碼結構 本來想學習一下jQuery的源碼,但由于jQuery的源碼有10000多行,設計相當復雜,所以決定從zepto開始,分析一個成熟的框架的代碼結構及執行步驟。 網上也有很多zepto的源碼分析,有的給源碼添加注釋,有的談與jQuery的不同,...
摘要:此模塊包含的設計思路即為預以匹配降級方案。沒有默認編譯該模塊,以及利用該模塊判斷后提供平臺相關邏輯的主要原因在于其設計原則的代碼完成核心的功能。此處,也引出了代碼實現的另一個基本原則面向功能標準,先功能覆蓋再優雅降級。 在進入 Zepto Core 模塊代碼之前,本節簡略列舉 Zepto 及其他開源庫中一些 Polyfill 的設計思路與實現技巧。 涉及模塊:IE/IOS 3/Dete...
閱讀 2941·2021-10-14 09:42
閱讀 3706·2021-08-11 11:19
閱讀 3552·2019-08-30 13:57
閱讀 3132·2019-08-30 13:49
閱讀 1545·2019-08-29 18:38
閱讀 905·2019-08-29 13:16
閱讀 1861·2019-08-26 13:25
閱讀 3235·2019-08-26 13:24