首頁  >  文章  >  web前端  >  jquery的整體架構分析及實作範例詳解_jquery

jquery的整體架構分析及實作範例詳解_jquery

WBOY
WBOY原創
2016-05-16 16:31:571227瀏覽

jQuery整體框架甚是複雜,也不易讀懂,這幾天一直在研究這個笨重而強大的框架。 jQuery的整體架構可分為:入口模組、底層模組和功能模組。這裡,我們以jquery-1.7.1為例進行分析。

jquery的整體架構

複製程式碼 程式碼如下:

16 (function( window, undefined ) {
         // 建構 jQuery 物件
  22     var jQuery = (function() {
  25         var jQuery = function( selector, context ) {
  27                 return new jQuery.fn.init( selector, context, rootjQuery );
  28             },
                 // 一堆局部變項宣告
  97         jQuery.fn = jQuery.prototype = {
  98             constructor: jQuery,
  99             init: function( selector, context, rootjQuery ) { ... },
                 // 一堆原型屬性與方法
 319         };
 322         jQuery.fn.init.prototype = jQuery.fn;
 324         jQuery.extend = jQuery.fn.extend = function() { ... };
 388         jQuery.extend({
                 // 一堆靜態屬性與方法
 892         });
 955         return jQuery;
 957     })();
          // 省略其他模組的程式碼 ...
9246     window.jQuery = window.$ = jQuery;
9266 })( window );

分析一下以上程式碼,我們發現jquery採取了匿名函數自執行的寫法,這樣做的好處就是可以有效的防止命名空間與變數污染的問題。縮寫以上程式碼就是:

複製程式碼 程式碼如下:

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

參數window

匿名函式傳了兩個參數進來,一個是window,一個是undefined。我們知道,在js中變數是有作用域鏈的,這兩個變數的傳入就會變成匿名函數的局部變量,而訪問起來的時候速度會更快。透過傳入window物件可以使window物件作為局部變數使用,那麼,函數的參數也都變成了局部變量,當在jquery中存取window物件時,就不需要將作用域鏈退回到頂層作用域,從而可以更快的存取window物件。

參數undefined

js在尋找變數的時候,js引擎首先會在函數本身的作用域中查找這個變量,如果沒有的話就繼續往上找,找到了就返回該變量,找不到就返回undefined。 undefined是window物件的屬性,透過傳入undefined參數,但又不進行賦值,可以縮短尋找undefined時的作用域鏈。在 自呼叫匿名函數 的作用域內,確保undefined是真的未定義。因為undefined能夠被重寫,賦予新的值。

jquery.fn是啥?

複製程式碼 程式碼如下:

 jQuery.fn = jQuery.prototype = {
              constructor: jQuery,
              init: function( selector, context, rootjQuery ) { ... },
                 // 一堆原型屬性與方法
        };

透過分析以上程式碼,我們發現jQuery.fn即是jQuery.prototype,這樣寫的好處就是更簡短。之後,我們又看到jquery為了簡潔,乾脆使用一個$符號來代替jquery使用,因此,在我們使用jquery框架的使用經常都會用到$(),

建構子jQuery()

圖片描述

jQuery的物件並不是透過 new jQuery 建立的,而是透過 new jQuery.fn.init 建立的:

複製程式碼 程式碼如下:

var jQuery = function( selector, context ) {

       return new jQuery.fn.init( selector, context, rootjQuery );

}

這裡定義了一個變數jQuery,他的值是jQuery建構函數,在955行(最上面的程式碼)回傳並賦值給jQuery變數

jQuery.fn.init

jQuery.fn (上面97行)是建構子jQuery()的原型對象,jQuery.fn.init()是jQuery原型方法,也可以稱為建構子。負責解析參數selector和context的類型並執行對應的查找。

參數context:可以不傳入,或傳入jQuery對象,DOM元素,普通js對象之一
參數rootjQuery:包含了document物件的jQuery對象,用於document.getElementById()尋找失敗等情況。

複製程式碼 程式碼如下:

jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
jQuery(selector [,context])

預設情況下,對符合元素的查找從根元素document 物件開始,即查找範圍是整個文檔樹,不過也可以傳入第二個參數context來限定它的查找範圍。例如:

複製程式碼 程式碼如下:

$('div.foo').click(function () {
            $('span',this).addClass('bar');//限定找出範圍,即上面的context
   });
jQuery.extend()和jQuery.fn.extend()

方法jQuery.extend(object)和jQuery.fn.extend(object)用於合併兩個或多個物件到第一個物件。相關原始碼如下(部分):

複製程式碼 程式碼如下:

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,//定義的一組局部變數
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

jQuery.extend(object); 為jQuery類別新增新增類別方法,可以理解為新增靜態方法。如:

複製程式碼 程式碼如下:

$.extend({
  add:function(a,b){returna b;}
});

便為 jQuery 添加一個為add 的 “靜態方法”,之後便可以在引入 jQuery 的地方,使用這個方法了,

$.add(3,4); //return 7
jQuery.fn.extend(object),查看一段官網的程式碼示範如下:

複製程式碼 程式碼如下:


<script><br />     jQuery.fn.extend({<br />         check: function() {<br />             return this.each(function() {<br />                 this.checked = true;<br />             });<br />         },<br />         uncheck: function() {<br />             return this.each(function() {<br />                 this.checked = false;<br />             });<br />         }<br />     });<br />     // Use the newly created .check() method<br />     $( "input[type='checkbox']" ).check();<br /> </script>

CSS選擇器引擎 Sizzle

可以說,jQuery是為操作DOM而誕生的,jQuery之所以如此強大,得益於CSS選擇器引擎 Sizzle,解析規則引用網上的一段實例:

selector:"div > p div.aaron input[type="checkbox"]"

解析規則:
1 依照從右到左
2 取出最後一個token  如[type="checkbox"]
                            {
                               , matches : Array[3]
                                                                value  : "[type="                                )「]"
                            }
3 過濾類型 如果type是 > ~ 空 四種關係選擇器中的一種,則跳過,在繼續過濾
4 直到配對到為 ID,CLASS,TAG  中一種 , 因為這樣才能透過瀏覽器的介面索取
5 此時seed種子合集中就有值了,這樣把刷選的條件給縮的很小了
6 如果匹配的seed的集合有多個就需要進一步的過濾了,修正選擇器 selector: "div > p div.aaron [type="checkbox"]"
7 OK,跳到階段的編譯函數


deferred物件

開發網站的過程中,我們經常遇到某些耗時很長的javascript操作。其中,既有異步的操作(例如ajax讀取伺服器資料),也有同步的操作(例如遍歷一個大型陣列),它們都不是立即能得到結果的。

通常的做法是,為它們指定回呼函數(callback)。即事先規定,一旦它們運行結束,應該呼叫哪些函數。

但是,在回呼函數方面,jQuery的功能非常弱。為了改變這一點,jQuery開發團隊就設計了deferred物件。

簡單說,deferred物件就是jQuery的回呼函數解決方案。在英文中,defer的意思是"延遲",所以deferred物件的意思是"延遲"到未來某個點再執行。

回顧一下jQuery的ajax操作的傳統寫法:

複製程式碼 程式碼如下:
$.ajax({
   url: "test.html",
   success: function(){
     alert("哈哈,成功了!");
   },
   error:function(){
     alert("出錯!");
   }
 });


在上面的程式碼中,$.ajax()接受一個物件參數,這個物件包含兩個方法:success方法指定操作成功後的回呼函數,error方法指定操作失敗後的回呼函數。

$.ajax()操作完成後,如果使用的是低於1.5.0版本的jQuery,返回的是XHR對象,你沒法進行鍊式操作;如果高於1.5.0版本,返回的是deferred對象,可以進行鍊式操作。

現在,新的寫法是這樣的:

複製程式碼 程式碼如下:

$.ajax("test.html")
  .done(function(){ alert("哈哈,成功了!"); })
  .fail(function(){ alert("出錯啦!"); });

為多個操作指定回呼函數

deferred物件的另一個好處,就是它允許你為多個事件指定一個回呼函數,這是傳統寫法做不到的。

請看下面的程式碼,它用到了一個新的方法$.when():

複製程式碼 程式碼如下:

$.when($.ajax("test1.html"), $.ajax("test2.html"))

 .done(function(){ alert("哈哈,成功了!"); })

 .fail(function(){ alert("出錯啦!"); });

這段程式碼的意思是,先執行兩個動作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就執行done()指定的回調函數;如果有一個失敗或都失敗了,就執行fail()指定的回呼函數。

jQuery.Deferred( func ) 的實作原理

內部維護了三個回呼函數列表:成功回呼函數列表、失敗回呼函數列表、訊息回呼函數列表,其他方法則圍繞這三個列表進行操作和檢測。

jQuery.Deferred( func ) 的源碼結構:

複製程式碼 程式碼如下:

 jQuery.extend({

    Deferred: function( func ) {
            // 成功回呼函數清單
        var doneList = jQuery.Callbacks( "once memory" ),
            // 失敗回呼函數清單
            failList = jQuery.Callbacks( "once memory" ),
            // 訊息回呼函數清單
            progressList = jQuery.Callbacks( "memory" ),
            // 初始狀態
            state = "pending",
            // 非同步佇列的唯讀副本
            promise = {
                // done, fail, progress
                // state, isResolved, isRejected
                // then, always
                // pipe
                // promise          
            },
            // 非同步佇列
            deferred = promise.promise({}),
            key;
        // 新增觸發成功、失敗、訊息回呼函清單的方法
        for ( key in lists ) {
            deferred[ key ] = lists[ key ].fire;
            deferred[ key "With" ] = lists[ key ].fireWith;
        }
        // 新增設定狀態的回呼函數
        deferred.done( function() {
            state = "resolved";
        }, failList.disable, progressList.lock )
        .fail( function() {
            state = "rejected";
        }, doneList.disable, progressList.lock );
        // 若傳入函數參數 func,則執行。
        if ( func ) {
            func.call( deferred, deferred );
        }

        // 返回非同步佇列 deferred
        return deferred;
    },
}

jQuery.when( deferreds )

提供了基於一個或多個物件的狀態來執行回呼函數的功能,通常是基於具有非同步事件的非同步隊列。

jQuery.when( deferreds ) 的用法

如果傳入多個非同步隊列對象,方法 jQuery.when() 傳回一個新的主非同步隊列對象的唯讀副本,只讀副本將追蹤所傳入的非同步隊列的最終狀態。

一旦所有非同步隊列都變成成功狀態,「主「非同步隊列的成功回呼函數被呼叫;

如果其中一個非同步隊列變成失敗狀態,主非同步隊列的失敗回呼函數被呼叫。

複製程式碼 程式碼如下:

/*
請求 '/when.do?method=when1' 回傳 {"when":1}
請求 '/when.do?method=when2' 回傳 {"when":2}
請求 '/when.do?method=when3' 回傳 {"when":3}
*/
var whenDone = function(){ console.log( 'done', arguments ); },
    whenFail = function(){ console.log( 'fail', arguments ); };
$.when(
    $.ajax( '/when.do?method=when1', { dataType: "json" } ),
    $.ajax( '/when.do?method=when2', { dataType: "json" } ),
    $.ajax( '/when.do?method=when3', { dataType: "json" } )
).done( whenDone ).fail( whenFail );

圖片描述

非同步隊列 Deferred

解耦非同步任務和回呼函數

為 ajax 模組、佇列模組、ready 事件提供基礎功能。

原型屬性與方法

原型屬性與方法原始碼:

複製程式碼 程式碼如下:

  97 jQuery.fn = jQuery.prototype = {
  98     constructor: jQuery,
  99     init: function( selector, context, rootjQuery ) {}
 210     selector: "",
 213     jquery: "1.7.1",
 216     length: 0,
 219     size: function() {},
 223     toArray: function() {},
 229     get: function( num ) {},
 241     pushStack: function( elems, name, selector ) {},
 270     each: function( callback, args ) {},
 274     ready: function( fn ) {}, //
 284     eq: function( i ) {},
 291     first: function() {},
 295     last: function() {},
 299     slice: function() {},
 304     map: function( callback ) {},
 310     end: function() {},
 316     push: push,
 317     sort: [].sort,
 318     splice: [].splice
 319 };
 

屬性selector用來記錄jQuery尋找和過濾DOM元素時的選擇器表達式。
屬性.length表示目前jquery物件中元素的個數。
方法.size()傳回目前jquery物件中元素的個數,功能上等同於屬性length,但應該優先使用length,因為他沒有函數呼叫開銷。

.size()原始碼如下:

複製程式碼 程式碼如下:

size():function(){
    return this.length;
}

方法.toArray()將目前jQuery物件轉換為真正的數組,轉換後的數組包含了所有元素,其原始碼如下:

複製程式碼 程式碼如下:

toArray: function() {
        return slice.call( this );
    },

方法.get(index)傳回目前jQuery物件中指定位置的元素,或包含了全部元素的陣列。其源
碼如下:

複製程式碼 程式碼如下:

    get: function( num ) {
        return num == null ?

            // Return a 'clean' array
            this.toArray() :

            // Return just the object
            ( num     },


如果沒有傳入參數,則呼叫.toArray()傳回了包含有鎖元素的陣列;如果指定了參數index,則傳回單獨的元素,index從0開始計數,並且支援負數。

首先會判斷num是否小於0,如果小於0,則用length num重新計算下標,然後使用數組訪問操作符([])獲取指定位置的元素,這是支持下標為負數的一個小技巧;如果大於等於0,直接傳回指定位置的元素。

eg()和get()使用詳解jquery常用方法及使用範例總結

方法.each()用於遍歷當前jQuery對象,並在每個元素上執行回調函數。方法.each()內部透過簡單的呼叫靜態方法jQuery.each()實作:

複製程式碼 程式碼如下:

each: function( callback, args ) {
        return jQuery.each( this, callback, args );
    },

回呼函數是在當前元素為上下文的語境中觸發的,即關鍵字this總是指向當前元素,在回調函數中return false 可以終止遍歷。

方法.map()遍歷目前jQuery對象,在每個元素上執行回呼函數,並將回呼函數的回傳值放入一個新jQuery物件中。此方法常用於取得或設定DOM元素集合的值。

複製程式碼 程式碼如下:

map: function( callback ) {
        return this.pushStack( jQuery.map(this, function( elem, i ) {
            return callback.call( elem, i, elem );
        }));
    },

原型方法.pushStack()建立一個新的空jQuery對象,然後把DOM元素集合放入這個jQuery物件中,並保留對目前jQuery物件的參考。

原型方法.pushStack()是核心方法之一,它為以下方法提供支援:

jQuery物件遍歷:.eq()、.first()、.last()、.slice()、.map()。

DOM查找、過濾:.find()、.not()、.filter()、.closest()、.add()、.andSelf()。

DOM遍歷:.parent()、.parents()、.parentsUntil()、.next()、.prev()、.nextAll()、.prevAll()、.nextUnit()、.prevUnit() 、.siblings()、.children()、.contents()。

DOM插入:jQuery.before()、jQuery.after()、jQuery.replaceWith()、.append()、.prepent()、.before()、.after()、.replaceWith()。
定義方法.push( elems, name, selector ),它接受3個參數:

參數elems:將放入新jQuery物件的元素數組(或類別數組物件)。

參數name:產生元素數組elems的jQuery方法名。

參數selector:傳給jQuery方法的參數,用於修正原型屬性.selector。
方法.end()結束目前鏈條中最近的篩選操作,並將匹配元素還原為先前的狀態

複製程式碼 程式碼如下:

end: function() {
        return this.prevObject || this.constructor(null);
    },

傳回前一個jQuery對象,如果屬性prevObect不存在,則建構一個空的jQuery物件返回。方法.pushStack()用於入棧,方法.end()用於出棧

靜態屬性與方法

相關原始碼如下:

複製程式碼 程式碼如下:

388 jQuery.extend({
 389     noConflict:函數(深){},
 402     isReady:假,
 406     準備等待:1,
 409    holdReady:函數(維持){},
 418     準備:函數(等待){},
 444    bindReady:函數(){},
 第492章  第496章函數( obj ) {},
 501     isWindow: function( obj ) {},
 505     isNumeric: 函數( obj ) {},
 第509章 509  515     isPlainObject: function( obj ) {},
 第544章  551     錯誤:函數(味精){},
 555     parseJSON:函數(資料){},
 581     parseXML:函數(資料){},
 601     noop:函數(){},
 606     globalEval:函數(資料){},
 619     駝峰命名法:函數(字串){},
 623     節點名稱:函數(elem,名稱){},
 第628章  第669章 669函數(文本){}:函數(文本){},
 684     makeArray:函數(數組,結果){},
 第702章  第724章  第744章  第761章  794     指導:1,
 第798章  第825章  第852章  第858章  870     子:函數(){},
 891     瀏覽器:{}
 第892章 });
 


未完成待續、、、今天就先到這裡了,接下來補齊。別急哈小夥伴
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn