jQuery源碼解析(架構與相依模組)第一章 理解架構

來源:互聯網
上載者:User

標籤:匯入   ensure   var   style   原則   cts   anim   恢複   conflict   

1-1 jQuery設計理念

   引用百科的介紹:

jQuery是繼prototype之後又一個優秀的Javascript架構。它是輕量級的js庫 ,它相容CSS3,還相容各種瀏覽器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及後續版本將不再支援IE6/7/8瀏覽器。jQuery使使用者能更方便地處理HTML(標準通用標記語言 (SGML)下的一個應用)、events、實現動畫效果,並且方便地為網站提供AJAX互動。jQuery還有一個比較大的優勢是,它的文檔說明很全,而且各種應用也說得很詳細,同時還有許多成熟的外掛程式可供選擇。jQuery能夠使使用者的html頁面保持代碼和html內容分離,也就是說,不用再在html裡面插入一堆js來調用命令了,只需定義id即可。

The Write Less,Do More(寫更少,做更多),無疑就是jQuery的核心理念,簡潔的API、優雅的鏈式、強大的查詢與便捷的操作。從而把jQuery打造成前端世界的一把利劍,所向披靡!

簡潔的API: 

$.on$.css$.ajax….

優雅的鏈式:

var jqxhr = $.ajax( "example.php" )    .done(function() { alert("success"); })    .fail(function() { alert("error"); })    .always(function() { alert("complete"); });

強大的選取器:

$("div, span, p.myClass" )$("div span:first-child")$("tr:visible")…

便捷的操作:

$("p").removeClass("myClass noClass").addClass("yourClass");$("ul li:last").addClass(function(index) {   return"item-" + index;});$(‘.container‘).append($(‘h2‘));…

為什麼要做jQuery源碼解析?

雖然jQuery的文檔很完善,潛意識降低了前端開發的入門的門檻,要實現一個動畫隨手拈來,只要簡單的調用一個animate方法傳遞幾個執行的參數即可,但如果要我們自己實現一個定製的動畫呢?我們要考慮的問題太多太多了,瀏覽器安全色、各種屬性的擷取、邏輯流程、效能等等,這些才是前端開發的基礎核心。

如果我們只知道使用jQuery,而不知道其原理,那就是“知其然,而不知其所以然”,說了這麼多,那就趕快跟著慕課網進入“高大上”之旅吧,深入來探究jQuery的內部架構!

1-2 jQuery整體架構

任何程式碼不是一開始就複雜的,成功也不是一躇而蹴的,早期jQuery的作者John Resig在2005年提議改進Prototype的“Behaviour”庫時,只是想讓其使用更簡單才發布新的jQuery架構。起初John Resig估計也沒料想jQuery會如此的火熱。我們可以看到從發布的第一個1.0開始到目前最新的2.1.1其代碼膨脹到了9000多行,它相容CSS3,還相容各種瀏覽器,jQuery使使用者能更方便地處理DOM、事件、實現動畫效果,並且方便地為網站提供AJAX互動。

1、最新jQuery2.1.1版本的結構:
代碼請查看右側代碼編輯器(1-24行)

2、jQuery的模組依賴網:
 

(單擊圖片可放大)

jQuery一共13個模組,從2.1版開始jQuery支援通過AMD模組劃分,jQuery在最開始發布的1.0版本是很簡單的,只有CSS選擇符、事件處理和AJAX互動3大塊。其發展過程中,有幾次重要的變革:

    ?  1.2.3 版發布,引入資料緩衝,解決循環參考與大資料儲存的問題    ?  1.3 版發布,它使用了全新的選擇符引擎Sizzle,在各個瀏覽器下全面超越其他同類型JavaScript架構的查詢速度,程式庫的效能也因此有了極大提升    ?  1.5 版發布,新增延緩對像(Deferred Objects),並用deferred重寫了Ajax模組    ?  1.7 版發布,抽象出回調對象,提供了強大的的方式來管理回呼函數列表。

每一次大的改進都引入了一些新的機制、新的特性,通過這些新的機制就造就了如今jQuery庫,一共13個模組,模組不是單一的,比如jQuery動畫,都會依賴非同步隊列、動畫隊列、回調隊列與資料緩衝模組等。

jQuery抽出了所有可複用的特性,分離出單一模組,通過組合的用法,不管在設計思路與實現手法上jQuery都是非常高明的。

五大塊:
jQuery按我的理解分為五大塊,選取器、DOM操作、事件、AJAX與動畫,那麼為什麼有13個模組?因為jQuery的設計中最喜歡的做的一件事,就是抽出共同的特性使之“模組化”,當然也是更貼近S.O.L.I.D五大原則的“單一職責SRP”了,遵守單一職責的好處是可以讓我們很容易地來維護這個對象,比如,當一個對象封裝了很多職責的時候,一旦一個職責需要修改,勢必會影響該對象的其它職責代碼。通過解耦可以讓每個職責更加有彈性地變化。
我們來看看jQuery文檔針對業務層的Ajax的處理提供了一系列的門面介面:

.ajaxComplete().ajaxError().ajaxSend().ajaxStart().ajaxStop().ajaxSuccess()

底層介面:

jQuery.ajax()jQuery.ajaxSetup()

快捷方法:

jQuery.get()jQuery.getJSON()jQuery.getScript()jQuery.post()

 

jQuery介面的設計原理

商務邏輯是複雜多變的,jQuery的高層API數量非常多,而且也非常的細緻,這樣做可以更友好的便於開發人員的操作,不需要必須在一個介面上重載太多的動作。我們在深入內部看看Ajax的高層方法其實都是統一調用了一個靜態jQuery.ajax方法,代碼見右側代碼編輯器(27-43行)。
在jQuery.ajax的內部實現是非常複雜的,首先ajax要考慮非同步處理與回調的統一性,所以就引入了非同步隊列模組(Deferred)與回調模組(Callbacks), 所以要把這些模組方法在ajax方法內部再次封裝成、構建出一個新的jQXHR對象,針對參數的預設處理,資料轉送的格式化等等。

1-3 立即調用運算式

任何庫與架構設計的第一個要點就是解決命名空間與變數汙染的問題。jQuery就是利用了JavaScript函數範圍的特性,採用立即調用運算式包裹了自身的方法來解決這個問題。

jQuery的立即調用函數運算式的寫法有三種:

寫法1:

(function(window, factory) {    factory(window)}(this, function() {    return function() {       //jQuery的調用    }}))

可以看出上面的代碼中嵌套了2個函數,而且把一個函數作為參數傳遞到另一個函數中並且執行,這種方法有點複雜,我們簡化一下寫法:

寫法2:

var factory = function(){    return function(){        //執行方法    }}var jQuery = factory();

上面的代碼效果和方法1是等同的,但是這個factory有點變成了簡單的Factory 方法模式,需要自己調用,不像是一個單例的jQuery類,所以我們需要改成“自執行”,而不是另外調用。

寫法3:

(function(window, undefined) {    var jQuery = function() {}    // ...    window.jQuery = window.$ = jQuery;})(window);

從上面的代碼可看出,自動初始化這個函數,讓其只構建一次。詳細說一下這種寫法的優勢:

  1、window和undefined都是為了減少變數尋找所經過的scope範圍。當window通過傳遞給閉包內部之後,在閉包內部使用它的時候,可以把它當成一個局部變數,顯然比原先在window scope下尋找的時候要快一些。
  2、undefined也是同樣的道理,其實這個undefined並不是JavaScript資料類型的undefined,而是一個普普通通的變數名。只是因為沒給它傳遞值,它的值就是undefined,undefined並不是JavaScript的保留字。

 

有童鞋留言到,為什麼要傳遞undefined?

Javascript 中的 undefined 並不是作為關鍵字,因此可以允許使用者對其賦值。

我們看一個

var undefined = ‘慕課網‘;(function(window) {  alert(undefined);//IE8 ‘慕課網‘})(window)

IE8存在這個問題,當然,大部分瀏覽器都是不能被修改的

如果函數調用不傳遞,參數預設就是undefined

;(function(window,undefined) {    //undefined})(window)

 


jQuery為什麼要建立這樣的一個外層包裹,其原理又是如何?

這裡要區分2個概念一個是匿名函數,一個是自執行。顧名思義,匿名函數,就是沒有函數名的函數,也就是不存在外部參考。但是是否像下面代碼實現呢:

function(){//代碼邏輯}

上面這種寫法是錯了,聲明了它但是又不給名字又沒有使用,所以在文法上錯誤的,那麼怎麼去執行一個匿名的函數呢?
要調用一個函數,我們必須要有方法定位它、引用它。所以,我們要取一個名字:

var jQuery = function(){//代碼邏輯}

jQuery使用()將匿名函數括起來,然後後面再加一對小括弧(包含參數列表),那麼這小括弧能把我們的運算式組合分塊,並且每一塊(也就是每一對小括弧),都有一個傳回值。這個傳回值實際上也就是小括弧中運算式的傳回值。所以,當我們用一對小括弧把匿名函數括起來的時候,實際上小括弧返回的,就是一個匿名函數的Function對象。因此,小括弧對加上匿名函數就如同有名字的函數般被我們取得它的引用位置了。所以如果在這個引用變數後面再加上參數列表,就會實現普通函數的調用形式。

最後,我們回到寫法1看看jQuery利用寫法3的寫法,然後把整個函數作為參數傳遞給另外一個函數,主要是為了判斷jQuery在不同平台的下的載入邏輯,主流的庫一般都有對 AMD 和 CommonJS 的支援代碼,看看jQuery的代碼:

if (typeof module === "object" && typeof module.exports === "object") {    module.exports = global.document ?        factory(global, true) :        function(w) {            if (!w.document) {                throw new Error("jQuery requires a window with a document");            }            return factory(w);    };} else {    factory(global);}

總結:全域變數是魔鬼, 匿名函數可以有效保證在頁面上寫入JavaScript,而不會造成全域變數的汙染,通過小括弧,讓其載入的時候立即初始化,這樣就形成了一個單例模式的效果從而只會執行一次。

1-4  jQuery的類數組對象結構

為什麼是類數組對象呢?

很多人迷惑的jQuery為什麼能像數組一樣操作,通過對象get方法或者直接通過下標0索引就能轉成DOM對象。

首先我們看jQuery的入口都是統一的$, 通過傳遞參數的不同,實現了9種方法的重載:

1. jQuery([selector,[context]])2. jQuery(element)3. jQuery(elementArray)4. jQuery(object)5. jQuery(jQuery object)6. jQuery(html,[ownerDocument])7. jQuery(html,[attributes])8. jQuery()9. jQuery(callback)

9種用法整體來說可以分三大塊:選取器、dom的處理、dom載入。
換句話說jQuery就是為了擷取DOM、操作DOM而存在的!所以為了更方便這些操作,讓節點與執行個體對象通過一個橋樑給關聯起來,jQuery內部就採用了一種叫“類數組對象”的方式作為儲存結構,所以我們即可以像對象一樣處理jQuery操作,也能像數組一樣可以使用push、pop、shift、unshift、sort、each、map等類數組的方法操作jQuery對象了。

jQuery對象可用數組下標索引是什麼原理?

通過$(".Class")構建的對象結構如下所示:


                   
整個結構很明了,通過對象索引值對的關係儲存著屬性,原型儲存著方法。我們來簡單的類比一個這樣的資料結構:(請查看右側代碼編輯器)

以上是類比jQuery的對象結構,通過aQuery方法抽象出了對象建立的具體過程,這也是軟體工程領域中的廣為人知的設計模式-Factory 方法。

jQuery的無new構建原理

函數aQuery()內部首先保證了必須是通過new操作符構建。這樣就能保證當前構建的是一個帶有this的執行個體對象,既然是對象我們可以把所有的屬性與方法作為對象的key與value的方式給映射到this上,所以如上結構就可以類比出jQuery的這樣的操作了,即可通過索引取值,也可以鏈式方法取值,但是這樣的結構是有很大的缺陷的,每次調用ajQuery方法等於是建立了一個新的執行個體,那麼類似get方法就要在每一個執行個體上重新建立一遍,效能就大打折扣,所以jQuery在結構上的最佳化不僅僅只是我們看到的,除了實作類別數組結構、方法的原型共用,而且還實現方法的靜態與執行個體的共存,這是我們之後將會重點分析的。

1-5  jQuery中ready與load事件

jQuery有3種針對文檔載入的方法

$(document).ready(function() {    // ...代碼...})//document ready 簡寫$(function() {    // ...代碼...})$(document).load(function() {    // ...代碼...})

一個是ready一個是load,這兩個到底有什麼區別呢?

ready與load誰先執行:
大家在面試的過程中,經常會被問到一個問題:ready與load那一個先執行,那一個後執行?答案是ready先執行,load後執行。

DOM文檔載入的步驟:
要想理解為什麼ready先執行,load後執行就要先瞭解下DOM文檔載入的步驟:

(1) 解析HTML結構。(2) 載入外部指令碼和樣式表檔案。(3) 解析並執行指令碼代碼。(4) 構造HTML DOM模型。//ready(5) 載入圖片等外部檔案。(6) 頁面載入完畢。//load

從上面的描述中大家應該已經理解了吧,ready在第(4)步完成之後就執行了,但是load要在第(6)步完成之後才執行。

結論:

ready與load的區別就在於資源檔的載入,ready構建了基本的DOM結構,所以對於代碼來說應該越快載入越好。在一個高速瀏覽的時代,沒人願意等待答案。假如一個網站頁面載入超過4秒,不好意思,你1/4的使用者將面臨著流失,所以對於架構來說使用者體驗是至關重要的,我們應該越早處理DOM越好,我們不需要等到圖片資源都載入後才去處理架構的載入,圖片資源過多load事件就會遲遲不會觸發。

我們看看jQuery是如何處理文檔載入時機的問題:

jQuery.ready.promise = function( obj ) {    if ( !readyList ) {        readyList = jQuery.Deferred();        if ( document.readyState === "complete" ) {            // Handle it asynchronously to allow scripts the opportunity to delay ready            setTimeout( jQuery.ready );        } else {            document.addEventListener( "DOMContentLoaded", completed, false );            window.addEventListener( "load", completed, false );        }    }    return readyList.promise( obj );};

jQuery的ready是通過promise給封裝過的,這也是jQuery擅長的手法,統一了回調體系,以後我們會重點談到。
可見jQuery相容的具體策略:針對進階的瀏覽器,我們當前很樂意用DOMContentLoaded事件了,省時省力。

那麼舊的IE如何處理呢?

繼續看jQuery的方案:

// Ensure firing before onload, maybe late but safe also for iframesdocument.attachEvent( "onreadystatechange", completed );// A fallback to window.onload, that will always workwindow.attachEvent( "onload", completed );// If IE and not a frame// continually check to see if the document is readyvar top = false;try {    top = window.frameElement == null && document.documentElement;} catch(e) {}if ( top && top.doScroll ) {    (function doScrollCheck() {        if ( !jQuery.isReady ) {            try {                // Use the trick by Diego Perini                // http://javascript.nwbox.com/IEContentLoaded/                top.doScroll("left");            } catch(e) {                return setTimeout( doScrollCheck, 50 );            }            // detach all dom ready events            detach();            // and execute any waiting functions            jQuery.ready();        }    })();}

    如果瀏覽器存在 document.onreadystatechange 事件,當該事件觸發時,如果 document.readyState=complete 的時候,可視為 DOM 樹已經載入。不過,這個事件不太可靠,比如當頁面中存在圖片的時候,可能反而在 onload 事件之後才能觸發,換言之,它只能正確地執行於頁面不包含二進位資源或非常少或者被緩衝時作為一個備選吧。

針對IE的載入檢測

Diego Perini 在 2007 年的時候,報告了一種檢測 IE 是否載入完成的方式,使用 doScroll 方法調用,詳情可見http://javascript.nwbox.com/IEContentLoaded/。
原理就是對於 IE 在非 iframe 內時,只有不斷地通過能否執行 doScroll 判斷 DOM 是否載入完畢。在上述中間隔 50 毫秒嘗試去執行 doScroll,注意,由於頁面沒有載入完成的時候,調用 doScroll 會導致異常,所以使用了 try -catch 來捕獲異常。
結論:所以總的來說當頁面 DOM 未載入完成時,調用 doScroll 方法時,會產生異常。那麼我們反過來用,如果不異常,那麼就是頁面DOM載入完畢了。

這都是我們在第一時間內處理ready載入的問題,如果ready在頁面載入完畢後呢?

jQuery就必須針對這樣的情況跳過綁定了:

if ( document.readyState === "complete" ) {     // Handle it asynchronously to allow scripts the opportunity to delay ready     setTimeout( jQuery.ready ); }

直接通過查看readyState的狀態來確定頁面的載入是否完成了。這裡會給一個定時器的最小時間後去執行,主要保證執行的正確。

1-6 jQuery多庫共存處理

多庫共存換句話說可以叫無衝突處理。

總的來說會有2種情況會遇到:

   1、$太火熱,jQuery採用$作為命名空間,不免會與別的庫架構或者外掛程式相衝突。

   2、jQuery版本更新太快,外掛程式跟不上,導致不同版本對外掛程式的支援度不一樣。

出於以上的原因,jQuery給出瞭解決方案–– noConflict函數。

    引入jQuery運行這個noConflict函數將變數$的控制權讓給第一個實現它的那個庫,確保jQuery不會與其他庫的$對象發生衝突。
在運行這個函數後,就只能使用jQuery變數訪問jQuery對象。例如,在要用到$("aaron")的地方,就必須換成jQuery("aaron"),因為$的控制權已經讓出去了。

使用DEMO:

jQuery.noConflict();// 使用 jQueryjQuery("aaron").show();// 使用其他庫的 $()$("aaron").style.display = ‘block’;

   這個函數必須在你匯入jQuery檔案之後,並且在匯入另一個導致衝突的庫之前使用。當然也應當在其他衝突的庫被使用之前,除非jQuery是最後一個匯入的。

由於比較簡單,我們直接上代碼解說:

Var _jQuery = window.jQuery,    _$ = window.$;jQuery.noConflict = function( deep ) {    if ( window.$ === jQuery ) {        window.$ = _$;    }if ( deep && window.jQuery === jQuery ) {        window.jQuery = _jQuery;    }    return jQuery;};

    如果我們需要同時使用jQuery和其他JavaScript庫,我們可以使用 $.noConflict()把$的控制權交給其他庫。舊引用的$ 被儲存在jQuery的初始化; noConflict() 簡單的恢複它們。
    通過類似swap交換的概念,先把之前的存在的命名空間給緩衝起來,通過對比當前的命名空間達到交換的目的,首先,我們先判斷下當前的的$空間是不是被jQuery接管了,如果是則讓出控制權給之前的_$引用的庫,如果傳入deep為true的話等於是把jQuery的控制權也讓出去了。
    如果不通過noConflict處理的話其後果可想而知,香噴噴的$大家都“覬覦已久”。

 

jQuery源碼解析(架構與相依模組)第一章 理解架構

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.