jquery源碼分析(二)——架構設計

要學習一個庫首先的理清它總體架構:javascript

一、jQuery源碼大體架構以下:(基於 jQuery 1.11 版本,共計8829行源碼)
(21,94)                定義了一些變量和函數jQuery=function(){}
(96,280)        給jQuery添加一些方法和屬性,jQuery.fn=jQuery.prototype
(285,347)        extend:        jQuery的一些繼承方法        更容易進行後續的擴展                                                
(349,817)        jQuery.extend(): 擴展一些工具方法
(877,2856)        Sizzle:複雜選擇器的實現
(2880,3042) Callbacks:回調對象——》對函數的統一管理
(3043,3183)        Deferred:延遲對象——》對異步的統一管理
(3184,3295)        support:功能檢測
(3308,3652)        data():數據緩存
(3653,3797)        queue():隊列管理
(3083,4299)        attr(),prop() val() addClass():屬性操做
(4300,5128)        on() trigger():事件的相關方法
(5140,6057)        DOM操做:添加 刪除 獲取 包裝 篩選
(6058,6620)        css():針對樣式的操做
(6621,7854) 提交的數據和Ajax()的操做:ajax() load() getJSON()
(7855,8584)        animate():運動的方法
(8585,8792)        offset:位置與尺寸的方法 
(8826)                對外提供jQuery對象接口:window.jQuery=window.$=jQuery;css

 

貼出一位大牛司徒正美對jquery的剖析:(最近也在讀他框架設計一書,深深折服,這裏推薦下)html

jQuery強在它專一於DOM操做的思路一開始就是對的,之後就是不斷在兼容性,性能上進行改進。java

  • ajax 數據交互
  • attributes 屬性操做,共分className, 表單元素的value值,屬性與特徵四大塊。
  • callbacks 函數列隊
  • core 種子模塊,命名空間,鏈式結構,domReady,多庫共存。
  • css 樣式操做,引入DE大神的兩個偉大的hacks,基本上解決精確獲取樣式的問題。
  • data 數據存取。
  • deferred 異步列隊(三合一版的函數列隊)
  • dimensions 元素尺寸的設置讀取(來自社區)。
  • effects 動畫引擎
  • event 事件系統(基於DE大神的事件系統與社區的兩個插件)
  • exports AMD系統(被RequireJS做者說服加幾行代碼支持其東東)
  • manipulation 節點的操做
  • offset 元素offsetTop(Left)的設置讀取
  • queue 列隊模塊(deferred與data的合體)。
  • sizzle 從右到左進行解析的選擇器引擎。
  • support 特徵偵測
  • traversing 元素遍歷。

 

 

二、使用jQuery時,咱們常常會在原生DOM對象和jQuery對象之間產生困擾,爲何DOM對象沒有這個方法,jQuery實例對象能夠輕鬆實現某些功能。接下來看看jQuery實例對象是怎麼建立。node

(function( window, undefined ) {
    var jQuery = (function() {
       // 構建jQuery對象
       var jQuery = function( selector, context ) {
           return new jQuery.fn.init( selector, context, rootjQuery );
       }
   
       // jQuery對象原型
       jQuery.fn = jQuery.prototype = {
           constructor: jQuery,
    } init = jQuery.fn.init =
function( selector, context, rootjQuery ) { // selector有如下7種分支狀況: // DOM元素 // body(優化) // 字符串:HTML標籤、HTML字符串、#id、選擇器表達式 // 函數(做爲ready回調函數) // 最後返回僞數組 } init.prototype = jQuery.fn;// 合併內容到第一個參數中,後續大部分功能都經過該函數擴展 // 經過jQuery.fn.extend擴展的函數,大部分都會調用經過jQuery.extend擴展的同名函數 jQuery.extend = jQuery.fn.extend = function() {}; // 在jQuery上擴展靜態方法 jQuery.extend({ // ready bindReady // isPlainObject isEmptyObject // parseJSON parseXML // globalEval // each makeArray inArray merge grep map // proxy // access // uaMatch // sub // browser }); // 到這裏,jQuery對象構造完成,後邊的代碼都是對jQuery或jQuery對象的擴展 return jQuery; })(); window.jQuery = window.$ = jQuery; })(window);

從這段代碼能夠看出:jquery

(1)、 jQuery對象不是經過 new jQuery 建立的,而是經過 new jQuery.fn.init 建立的。ajax

(2)、jQuery對象就是jQuery.fn.init對象( new jQuery.fn.init( selector, context, rootjQuery );)編程

(3)、 jQuery.fn指向了jquery的原型(其實是用屬性名fn代替prototype,爲後面編程使用方便,要否則屢次使用prototype會很繞):數組

jquery 原型定義了jquery對象初始化init的一系列方法。緩存

(4)、接下來,定義在jQuery.fn.init,後面又加上這句init.prototype = jQuery.fn :即jQuery.fn.init的原型也指向了jQuery的原型,有點繞哈,合併下:

       jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype,實例說明一下,

這說明了咱們使用new jQuery.fn.init()建立的實例對象的原型就是jQuery的原型,這樣你就能夠在jquery實例對象就能夠直接調用jQuery這個類原型上掛載的方法了。

(5)、檢驗一個庫的好壞是看這個庫的擴展性和兼容性。jQuery.extend = jQuery.fn.extend = function() {}是jQuery對外提供擴展的方法

jQuery之因此如此強大,就是它能很容易實現類方法(雖然javascript沒有類的概念,但這裏仍是用類來理解下,但願不要誤解到讀者)和實例方法擴展。

 

下面分析下jQuery實例對象這麼建立的。

init = jQuery.fn.init = function( selector, context ) {
        var match, elem;

        // HANDLE: $(""), $(null), $(undefined), $(false)
        if ( !selector ) {
            return this;
        }

        // Handle HTML strings
        if ( typeof selector === "string" ) {
            if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];

            } else {
                match = rquickExpr.exec( selector );
            }

            // Match html or make sure no context is specified for #id
            if ( match && (match[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[1] ) {
                    context = context instanceof jQuery ? context[0] : context;

                    // scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );

                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                // HANDLE: $(#id)
                } else {
                    elem = document.getElementById( match[2] );

                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document #6963
                    if ( elem && elem.parentNode ) {
                        // Handle the case where IE and Opera return items
                        // by name instead of ID
                        if ( elem.id !== match[2] ) {
                            return rootjQuery.find( selector );
                        }

                        // Otherwise, we inject the element directly into the jQuery object
                        this.length = 1;
                        this[0] = elem;
                    }

                    this.context = document;
                    this.selector = selector;
                    return this;
                }

            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }

        // HANDLE: $(DOMElement)
        } else if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return typeof rootjQuery.ready !== "undefined" ?
                rootjQuery.ready( selector ) :
                // Execute immediately if ready is not present
                selector( jQuery );
        }

        if ( selector.selector !== undefined ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

        return jQuery.makeArray( selector, this );
    };

 分析上面代碼:

一、jQuery.fn.init的功能是對傳進來的selector參數進行分析,進行各類不一樣的處理,而後建立jQuery實例對象。

二、arguments:選擇器和使用的上下文環境(比較生澀:舉個例子,好比在地圖上找深圳這個地方,選擇器比如深圳這個地名,熟悉的人可能一會兒用經緯度定位了,東經­113°46'~114°37',北緯22°27'~22°52'(參數context都不用傳了^^),對歪國仁來講,可能第一眼的先定位到咱們的公雞版圖:也就是context:china,這樣搜索速度就加快了。對國人(閉眼也能找着祖國哈^^)來講直接找廣東—靠海—靠香港:也就是context:廣東)。

三、接下來看看怎麼分析selector參數類型以及相應的作了什麼處理。

問題來了,看到這個正則這麼長有沒有要暈,要吐的感受,

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,這裏寫的什麼。呃這個暫時不解釋,後面專門奉上一個正則表達專題供你們解讀…………。

(1)  $(""), $(null), $(undefined), $(false);這幾種狀況直接返回 this,這裏this是什麼呢? 注意不是JQuery自己,而是jQuery.fn.init

(2)、字符串類型

a:HTML標籤形式的——$(<>):經過document.createElement 建立節點。相似 $("<div></div>")、$("<div>aaa</div><div>aaa</div>") 兩種狀況;

而其餘狀況如:$(「</div>111」)、$("#id")、$(".class")、$("div")、$("#id .class div"),其中requickExpr匹配的是$(「</div>111」)、$("#id")。若是傳入的是$(「</div>111」),則match=["</div>111","</div>",null];若是傳入的是$("#id"),則match=["#id",null,「id」]。

b:HTML字符串——$(html) -> $(array):建立DOM並擴充到jQuery對象

c:#id——$(#id):直接經過document.getElementById獲取dom節點。

d:選擇器表達式——$(expr, $(...)):

(3)、DOMElement:直接返回將該對象,只是修改了context屬性和length屬性。

(4)、function:用過jquery基本上都會初始化代碼寫到 $(function(){……})裏,就是DOMLoaded 加載完畢以後回調執行改匿名函數。

(5)、slector.slector = undefined 這個眼戳暫時沒看懂,有誰看懂提下謝謝 。

 

上面涉及到的函數這裏先提早腦補一下:$.parseHTML 、$.merge、$.isPlainObject(

$.parseHTML :將字符串轉換爲存儲DOM節點的數組 。第一個參數爲傳入的字符串,第二個爲指定的根節點,第三個是boolean值 (「<script></script>」是否能夠轉化爲<script>標籤),默認爲false,不轉換。

$.merge:合併兩個數組,在jQuery內部不只能夠合併數組,也能夠合併類數組。

$.isPlainObject():判斷傳入的參數是不是由 {}或new Object 建立的對象。

 

 

 三、下面看看jQuery原型掛載了什麼?

jQuery.fn = jQuery.prototype = {
    // The current version of jQuery being used
    jquery: version,

    constructor: jQuery,

    selector: "",

    length: 0,

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

    get: function( num ) {
        return num != null ?
            ( num < 0 ? this[ num + this.length ] : this[ num ] ) :

            slice.call( this );
    },

    pushStack: function( elems ) {

        var ret = jQuery.merge( this.constructor(), elems );

        ret.prevObject = this;
        ret.context = this.context;

        return ret;
    },

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

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

    slice: function() {
        return this.pushStack( slice.apply( this, arguments ) );
    },

    first: function() {
        return this.eq( 0 );
    },

    last: function() {
        return this.eq( -1 );
    },

    eq: function( i ) {
        var len = this.length,
            j = +i + ( i < 0 ? len : 0 );
        return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
    },

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

    push: push,
    sort: deletedIds.sort,
    splice: deletedIds.splice
};

一、先來看這三個屬性:push、sort、splice 都是數組 Array()原型上掛載的方法。僅供jQuery內部使用(私有方法),與jQuery外部工具方法(對外可以使用)不一樣

二、first、last、eq這幾個方法都是都是又來獲取數組某個元素的。

三、重點來了:pushStack在jQuery中使用頻率特別高,它是幹嗎的?只看這段代碼。只能知道 它調用了 jQuery.merge,合併了類數組。

 

jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    if ( typeof target === "boolean" ) {
        deep = target;

        target = arguments[ i ] || {};
        i++;
    }

    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
        target = {};
    }

    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {
        if ( (options = arguments[ i ]) != null ) {
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                if ( target === copy ) {
                    continue;
                }

                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    target[ name ] = jQuery.extend( deep, clone, copy );

                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }
    return target;
};

一、功能:jQuery.extend(object); 爲擴展jQuery類自己.爲類添加新的方法。jQuery.fn.extend(object);給jQuery實例對象添加方法。

二、結合$.extend使用方法來分析:

(1)、var newSrc=$.extend({},src1,src2,src3...)
(2)、var newSrc=$.extend(false,{},src1,src2,src3...)//嵌套子對象不拷貝
(3)、var newSrc=$.extend(true,{},src1,src2,src3...)

從arguments能夠看出,extend是爲了合併兩個或更多對象的屬性到第一個對象中。

三、再來分析代碼:

      看第一個邏輯意思是:若是第一個參數是布爾型的話,要實現擴展的對象target就是第二個參數,接下來再判斷此時target是否對象或函數,都不是則默認擴展到{}。

接下來兩個for循環,第一個循環遍歷後續全部對象,第二個循環 實現將每一個對象屬性的copy到第一個對象。看似很簡單,問題來了,target的屬性與須要copy對象屬性同樣時,怎麼處理?是直接覆蓋/重寫,仍是合併?這就涉及到了淺拷貝與深拷貝,那淺拷貝與深拷貝又是什麼。

(1)、js對象淺拷貝簡單的賦值就是淺拷貝。由於對象和數組在賦值的時候都是引用傳遞。賦值的時候只是傳遞一個指針。也就是說遇到同名屬性時直接覆蓋/重寫。

(2)、由於對象相對較爲複雜,因此咱們先來看對數組的深拷貝的問題

來看個例子,說明一下

extend(boolean,dest,src1,src2,src3...)

 第一個參數boolean表明是否進行深度拷貝,其他參數和前面介紹的一致,什麼叫深層拷貝,咱們看一個例子:

var result=$.extend( true,  {},  
{ name: "John", location: {city: "Boston",county:"USA"} },
{ last: "Resig", location: {state: "MA",county:"China"} } );

咱們能夠看出src1中嵌套子對象location:{city:"Boston"},src2中也嵌套子對象location:{state:"MA"},第一個深度拷貝參數爲true,那麼合併後的結果就是:

result={name:"John",last:"Resig",location:{city:"Boston",state:"MA",county:"China"}} ////淺拷貝,對copy對象同名屬性值類型爲數組或對象的屬性進行合併

也就是說它會將src中的嵌套子對象也進行合併,

而若是第一個參數boolean爲false,咱們看看合併的結果是什麼,以下:

var result=$.extend( false, {},  
{ name: "John", location:{city: "Boston",county:"USA"} },
{ last: "Resig", location: {state: "MA",county:"China"} } );

     那麼合併後的結果就是:

  result={name:"John",last:"Resig",location:{state:"MA",county:"China"}}//淺拷貝,對copy對象同名屬性值類型爲數組或對象的屬性直接覆蓋/重寫。

來看下深拷貝和前拷貝怎麼實現的:

if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    target[ name ] = jQuery.extend( deep, clone, copy );

                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }

(1)、淺拷貝:target[ name ] = copy;直接覆蓋和容易理解。

(2)、深拷貝稍顯複雜,首先判斷是數組仍是對象,在對嵌套子對象遞歸調用$.extend方法。

 

四、jQuery多庫共存處理

 $符號已經被不少庫做爲做爲命名空間,所以難免會與別的庫框架或者插件相沖突。

  jQuery引入noConflict函數能夠將變量$的控制權讓給第一個實現它的那個庫,確保jQuery不會與其餘庫的$對象發生衝突。在運行這個函數後,就只能使用jQuery變量訪問jQuery對象。下面來看看代碼如何處理的?

var
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,//外部庫重寫了jQuery,先緩存

	// Map over the $ in case of overwrite
	_$ = window.$;//外部庫重寫了$,先緩存起來

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {//把全局$的控制權交出去,此處,jQuery爲局部變量
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {//把全局jQuery的控制權也交出去,此處局部jQuery與全局jQuery作對比
		window.jQuery = _jQuery;
	}

	return jQuery;
};

  若是咱們須要同時使用jQuery和其餘JavaScript庫,咱們可使用 $.noConflict()把$的控制權交給其餘庫。舊引用的$ 被保存在jQuery的初始化; noConflict() 簡單的恢復它們。
    經過相似swap交換的概念,先把以前的存在的命名空間給緩存起來_$,_jQuery,經過對比當前的命名空間達到交換的目的,首先,咱們先判斷下當前的的$空間是否是被jQuery接管了,若是是則讓出控制權給以前的_$引用的庫,若是傳入deep爲true的話等因而把jQuery的控制權也讓出去了

 

那麼具體什麼時候調用纔不會引發庫命名空間衝突問題呢:這個函數必須在你導入jQuery文件以後,而且在導入另外一個致使衝突的庫以前使用。

相關文章
相關標籤/搜索