摘要:本來想學(xué)習(xí)一下的源碼,但由于的源碼有多行,設(shè)計相當(dāng)復(fù)雜,所以決定從開始,分析一個成熟的框架的代碼結(jié)構(gòu)及執(zhí)行步驟。同時發(fā)表在我的博客源碼分析代碼結(jié)構(gòu)
本來想學(xué)習(xí)一下jQuery的源碼,但由于jQuery的源碼有10000多行,設(shè)計相當(dāng)復(fù)雜,所以決定從zepto開始,分析一個成熟的框架的代碼結(jié)構(gòu)及執(zhí)行步驟。
網(wǎng)上也有很多zepto的源碼分析,有的給源碼添加注釋,有的談與jQuery的不同,但是都沒有系統(tǒng)的講解zepto框架的代碼結(jié)構(gòu)及初始化Zepto對象的過程。
準(zhǔn)備默認(rèn)你已經(jīng)對面向?qū)ο笥幸欢ǖ牧私猓疚氖沁厡?shí)踐邊寫的,雖有些亂,但好處是為大家提供了分析的思路。
英文文檔、 中文文檔
注意在文中$變量表示一個函數(shù)對象,而$()表示執(zhí)行函數(shù),他會返回另一個對象。
從文檔入手分析$在文檔中可以看到有兩類方法,其中一類是沒有$前綴,例如addClass,這些方法都有一個共同的特點(diǎn),操作DOM或BOM。還有一類是有前綴的例如$.trim,這一類方法無關(guān)平臺,只是封裝了一些常用的方法,可以看作ECMA層級的方法,與瀏覽器無關(guān)。
我們分別打印,看以下log日志
Title
console.log($.prototype); console.log($("#person")); console.log($);
結(jié)果如上圖,展開綠色1即可看到所有前綴的方法,展開圖中2可看到所有的不帶前綴的方法。圖中3返回的是一個函數(shù)。
1中的結(jié)果可以看出像$.trim這類方法保存在$.prototype的構(gòu)造函數(shù)中,也就是在$中,但是$打印出來的是卻一個函數(shù),為了解決這中迷惑性,以下代碼重現(xiàn)了這種情況,可以看出,$確實(shí)是一個函數(shù),只是這個函數(shù)多了一些特定的方法。$.trim只是$的一個屬性。
2中的方法都在對象的原型函數(shù)中,因為它執(zhí)行了$()函數(shù)返回了一個對象Z,該對象的原型中包含一些類似于addClass方法。
var good = (function() { var g; var log = function(text){ console.log(text); } g = function(){console.log("666");} g.log = log; return g; })(); console.log(good.log("Are you OK?"));// Are you OK? console.log(good);// function(){console.log("666");}
寫到這里突然想起來console對象還有個dir方法,console.dir($)清晰明了。-_-||
補(bǔ)充一張動圖
我認(rèn)為這種寫法的好處有,調(diào)用$()后返回的對象是一個新對象,就沒有類似$.trim這類方法,且addClass這類方法都在原型函數(shù)中,更能節(jié)省內(nèi)存。
不執(zhí)行$函數(shù)對象,只是調(diào)用其函數(shù)中的特定屬性,該對象只會創(chuàng)建一次(在引入zepto時就已經(jīng)初始化了),同樣不會浪費(fèi)內(nèi)存。
兩種類型的方法共用同一個變量名,減少命名沖突的可能,封裝更徹底。
下面開始自上而下的分析源碼,層層剝離,使脈絡(luò)清晰。
一、閉包返回與全局變量var Zepto = (function() { return $ })() window.Zepto = Zepto window.$ === undefined && (window.$ = Zepto)
吐槽一下源碼中不寫分號,總感覺怪怪的。
使用自執(zhí)行匿名函數(shù)返回$傳遞給Zepto。然后把Zepto和$作為window的屬性。
這樣對外只有兩個或一個變量可以使用,不會污染全局環(huán)境,如果命名沖突,只需改源碼中最后兩行即可。
二、核心架構(gòu)var Zepto = (function() { var $,zepto = {}; $.trim = function(str) { return str == null ? "" : String.prototype.trim.call(str) } $ = function(selector, context){ return zepto.init(selector, context) } $.fn = { addClass: function(name){ // 省略 } // 省略 } zepto.Z.prototype = Z.prototype = $.fn $.zepto = zepto return $ })()
在此可以看到$.trim與addClass與其他變量或?qū)傩缘年P(guān)系,去除這兩個屬性后就是第二層的架構(gòu),如下。
var $,zepto = {};
初始化了一個$變量和zepto對象,注意這里是小寫
$函數(shù)
這個函數(shù)調(diào)用zepto.init方法,返回對象,在之后會講解。
添加$.fn對象
它擁有Zepto對象上所有可用的方法(官方文檔),這里可能有誤解,應(yīng)該是擁有由$()返回的對象的所有方法,里面的方法在$("#person").prototype中看到過
zepto.Z.prototype = Z.prototype = $.fn
Z.prototype = $.fn如果你仔細(xì)觀察開始時的$("#person")返回的對象其實(shí)就是Z,那么經(jīng)過$()返回的對象的原型指向了擁有大量方法的$.fn對象,所以才可以在$("#person").prototype中看到過addClass方法
然后是zepto.Z.prototype = $.fn,請參考zepto源碼中關(guān)于zepto.Z.prototype = $.fn的問題
$.zepto = zepto
不知道為什么有這一句,似乎是可以通過$.zepto訪問內(nèi)部的方法,例如$.zepto.isZ($("#person"))。又或許是想將其封裝為$的屬性。
return $
可以清楚的看到內(nèi)部的結(jié)構(gòu),$.fn、$.zepto、$.trim都作為$對象的屬性存在,如果調(diào)用$()函數(shù),返回的Z對象就擁有指定的原型鏈Z.prototype = $.fn。
那么問題又來了:zepto.init方法是做什么的?執(zhí)行$()函數(shù)返回的是什么對象?
三、框架的入口:zepto.init還是先上源碼
$ = function(selector, context){ return zepto.init(selector, context) } zepto.init = function(selector, context) { var dom if (!selector) return zepto.Z()// 如果是$()或$("")則執(zhí)行 else if (typeof selector == "string") {// 如果傳入的是字符串 selector = selector.trim()// 去除收尾空白符 if (selector[0] == "<" && fragmentRE.test(selector))// 如果傳入的字符串是以<開頭且符合HTML代碼規(guī)則(用了正則表達(dá)式),即創(chuàng)建元素 dom = zepto.fragment(selector, RegExp.$1, context), selector = null// 創(chuàng)建一個DOM對象 else if (context !== undefined) return $(context).find(selector)// 這里其實(shí)是一種討巧的辦法,我相信jQuery中肯定不會這么寫,目的是實(shí)現(xiàn)在指定范圍內(nèi)查找[context]元素 else dom = zepto.qsa(document, selector)// 調(diào)用zepto.qsa解析字符串,返回一個DOM數(shù)組 } else if (isFunction(selector)) return $(document).ready(selector)// 很簡單,如果是函數(shù),則在文檔就緒后執(zhí)行 else if (zepto.isZ(selector)) return selector// 如果是一個zepto對象,直接返回 else { if (isArray(selector)) dom = compact(selector)// 如果是數(shù)組,調(diào)用compact返回一個數(shù)組,最后經(jīng)Z變成類數(shù)組對象,我想這里是把幾個DOM對象作為數(shù)組的參數(shù)傳入,返回一個類數(shù)組對象 else if (isObject(selector))// 如果是一個對象,將其包含在數(shù)組之內(nèi),如p = document.getElementById("#p");$(p); dom = [selector], selector = null 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) } return zepto.Z(dom, selector)// 可以看這里,無論以上過程經(jīng)歷了什么,都要經(jīng)過此函數(shù),目的是將數(shù)組轉(zhuǎn)化為類數(shù)組對象。 } zepto.Z = function(dom, selector) { return new Z(dom, selector) } /** * 一個構(gòu)造函數(shù),將dom對象中的屬性和方法都復(fù)制到this下,并添加了兩個屬性,length和selector,這個函數(shù)的目的是將DOM對象轉(zhuǎn)化為供zepto使用的類數(shù)組對象 */ 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.fragment = function(html, name, properties) { // 這里的代碼就不展開了,其作用是返回一個DOM對象,若$()中傳入第二個參數(shù),則將其屬性添加給創(chuàng)建的DOM對象 return dom } zepto.qsa = function(element, selector){ // 這里也不展開代碼,又興趣的可以直接看源碼,很簡單,無非是根據(jù)傳入的選擇符分別調(diào)用getElementByID、getElementsByTagName、getElementsByClassName、querySelectorAll等方法,返回一個數(shù)組,數(shù)組的值即是DOM對象,這就是最核心的選擇器,有點(diǎn)坑爹。 }
這一段代碼是zepto的核心代碼,是使用zepto的入口,這里還是從文檔入手比較好理解。
Zepto集合是一個類似數(shù)組的對象,它具有鏈?zhǔn)椒椒▉聿僮魉赶虻腄OM節(jié)點(diǎn),除了$(Zepto)對象上的直接方法外(如$.extend),文檔對象中的所有方法都是集合方法。
上一句可以告訴我們:Zepto集合是一個類似數(shù)組的對象即是之前$("#person")返回的對象。文檔中所有不帶前綴的方法叫做集合方法。
再來看方法的調(diào)用
$(selector, [context]) // 在指定范圍內(nèi)查找[context]元素,類似$(context).find(selector),例如$(".logo",".header"),只選取.header類中的.logo類 $() // 這里應(yīng)該是指傳入zepto對象,如:var a = $("a");$(a); $( ) // 選取所有頁面中的div元素,如:$("div") $(htmlString) // 創(chuàng)建一個元素,如:$(" Hello
") $(htmlString, attributes) // 創(chuàng)建帶有屬性的元素,$("", { text:"Hello", id:"greeting", css:{color:"darkblue"} }) Zepto(function($){ ... }) // 當(dāng)頁面ready的時候,執(zhí)行回調(diào) // 還有寫文檔中沒有 $()或$("")
至于為什么調(diào)用Z()函數(shù)返回的對象都以Z為對象名呢?看這段小代碼就可以明白
function Z(){ this.name = 666; } z = new Z(); console.log(z);// 返回的對象名為Z
這個過程比較復(fù)雜,建議你親自動手試一試,還是以$("#person")為例,用Chrome在這一行打斷點(diǎn),然后步進(jìn),我看能不能做個flash圖。
如果你仔細(xì)觀察,這一層的核心源碼最后大部分都有return返回,最終$()也會返回對象,整個過程其實(shí)是對向$()中傳入的參數(shù)進(jìn)行處理運(yùn)算,最終返回一個zepto自己創(chuàng)造的對象,然后用于后續(xù)操作。
zepto的源碼對一般的熟練面向?qū)ο蟮娜藖碚f是非常簡單的,對于有面向?qū)ο蟾拍顩]有寫過的人來說是那種踮起腳尖能得到的難度。最開始想學(xué)習(xí)jQuery源碼,但看了一點(diǎn)覺得太復(fù)雜,于是投機(jī)取巧看zepto,也是完全理不清頭緒啊,知道上個星期天找到了一種方法,寫一端小代碼,然后在Chrome里步進(jìn)調(diào)試,看函數(shù)之間的依賴關(guān)系法,看函數(shù)的傳入值,返回值,了解這個函數(shù)是做什么用的。最后慢慢的理清頭緒。這篇文章在星期一就開始寫,一直到星期四才算完成。
$(或Zepto)是一個函數(shù)對象,但他包含了一些特定的屬性(方法)。可以直接調(diào)用這些屬性(方法),這些屬性(方法)大都與瀏覽器無關(guān)。也可以執(zhí)行$函數(shù),執(zhí)行后返回一個類數(shù)組對象,這個對象的原型中包含一些操作DOM的方法,向原型中添加屬性(方法),所有的對象都可以訪問到。執(zhí)行$函數(shù)是zepto的關(guān)鍵代碼,其目的是根據(jù)傳入函數(shù)的變量值,加工處理成類數(shù)組對象并返回,用于后續(xù)操作。
同時發(fā)表在我的博客:《zepto源碼分析-代碼結(jié)構(gòu)》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/80927.html
摘要:源碼結(jié)構(gòu)整體結(jié)構(gòu)如果在編輯器中將的源碼折疊起來,看到的就跟上面的代碼一樣。參考源碼分析代碼結(jié)構(gòu)對象思想與源碼分析設(shè)計和源碼分析源碼中關(guān)于的問題最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關(guān)注歡迎提意見 雖然最近工作中沒有怎么用 zepto ,但是據(jù)說 zepto 的源碼比較簡單,而且網(wǎng)上的資料也比較多,所以我就挑了 zepto 下手,希望能為以后閱讀其他框架的源碼打下基礎(chǔ)吧。 源碼版...
摘要:選擇的理由是一個用于現(xiàn)代瀏覽器的與大體兼容的庫。環(huán)境搭建分析環(huán)境的搭建僅需要一個常規(guī)頁面和原始代碼一個常規(guī)頁面打開的首頁即可,在開發(fā)人員工具中即可使用原始代碼本篇分析的代碼參照,進(jìn)入該代碼分支中即可。 選擇 Zepto 的理由 Zepto is a minimalist JavaScript library for modern browsers with a largely jQue...
摘要:對象構(gòu)造函數(shù)讀入的兩個參數(shù)與在中也有明確的規(guī)范,用以保證構(gòu)造函數(shù)的簡單性。 承接第三篇末尾內(nèi)容,本篇結(jié)合官方 API 進(jìn)入對 Zepto 核心的分析,開始難度會比較大,需要重點(diǎn)理解幾個核心對象的關(guān)系,方能找到線索。 $() 與 Z 對象創(chuàng)建 Zepto Core API 的首個方法 $() 按照其官方解釋: Create a Zepto collection object by pe...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個判斷需要引入設(shè)備偵測模塊。然后是監(jiān)測事件,根據(jù)這三個事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
摘要:承接第一篇末尾內(nèi)容,本部分開始進(jìn)入主模塊,分析其設(shè)計思路與實(shí)現(xiàn)技巧下文代碼均進(jìn)行過重格式化,但代碼版本同第一部分內(nèi)容且入口函數(shù)不變的選擇器先從第一個與原型鏈構(gòu)造不直接相關(guān)的工具函數(shù)說起,觀察的設(shè)計思路。 承接第一篇末尾內(nèi)容,本部分開始進(jìn)入 zepto 主模塊,分析其設(shè)計思路與實(shí)現(xiàn)技巧(下文代碼均進(jìn)行過重格式化,但代碼 Commit 版本同第一部分內(nèi)容且入口函數(shù)不變): Zepto 的選...
閱讀 2694·2021-11-08 13:16
閱讀 2373·2021-10-18 13:30
閱讀 2241·2021-09-27 13:35
閱讀 1997·2019-08-30 15:55
閱讀 2446·2019-08-30 13:22
閱讀 587·2019-08-30 11:24
閱讀 2083·2019-08-29 12:33
閱讀 1817·2019-08-26 12:10