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