jQuery3.3.1源碼閱讀(一)

1.入口結構html

( function( global, factory ) {
        "use strict";
        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 );
        }
    } )( 
        typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
        //具體代碼
    }

抽離結構以下:node

( function() { })()

涉及到的知識jquery

  • js解析函數時的規則
  • 函數定義和函數表達式
  • js閉包

js解析函數的規則windows

  • js解析器會在遇到function時將其認爲是函數定義而非函數表達式

函數定義和函數表達式數組

  • 函數定義:function test(){ }
  • 函數表達式:let test = function(){ }

js閉包閉包

  • 閉包:函數中的函數,本質是指做用域內的做用域
//閉包舉例
function f(){
    var a = 2;
    function g(){
        console.log(a)
    };
    return g;
}
f()();

綜合以上的內容,再來看一下剛纔抽離出來的代碼app

(function(){ })()
  • 第一個括號有兩個做用:函數

    1. 讓js解析器把後面的function看成函數表達式而不是函數定義
    2. 造成一個做用域,相似在上面閉包例子中的f函數
  • 第二個括號ui

    1. 觸發函數並傳參

2.第二個括號內的參數有哪些?this

(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { })
  • 第一個參數是判斷環境,傳入全局對象
  • 第二個參數是肯定環境後具體執行的代碼

3.第一個括號內的函數作了什麼?

( function( global, factory ) {
    "use strict";
    //判斷是否是在commonjs環境下,若是是就執行如下代碼
    if ( typeof module === "object" && typeof module.exports === "object" ) {
        //判斷是否支持global.document
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                //不支持global.document時報錯
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                //報錯後返回jquery(w)
                return factory( w );
            };
    } else {
        //windows環境下執行這個代碼
        factory( global );
    }
} )

4.判斷完環境後經過:factory( global );跳轉到第二個括號的第二個參數內執行具體的內容

//總體的結構抽離以下
function( window, noGlobal ) {
    "use strict";
    //具體的jquery內部代碼
    
    if ( !noGlobal ) {
        //在window下能夠用如下方式調用
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
}

5.進入函數後,先定義了一些變量,函數和對象(能夠跳過先看下面的內容)

//定義了一些變量和方法
var arr = [];

var document = window.document;

var getProto = Object.getPrototypeOf;

//數組方法簡寫

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

//對象方法簡寫
var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

var support = {};

//定義函數
var isFunction = function isFunction( obj ) {
      return typeof obj === "function" && typeof obj.nodeType !== "number";
  };


var isWindow = function isWindow( obj ) {
        return obj != null && obj === obj.window;
    };

function DOMEval( code, doc, node ) {
    doc = doc || document;

    var i,
        script = doc.createElement( "script" );

    script.text = code;
    if ( node ) {
        for ( i in preservedScriptAttributes ) {
            if ( node[ i ] ) {
                script[ i ] = node[ i ];
            }
        }
    }
    doc.head.appendChild( script ).parentNode.removeChild( script );
}

function toType( obj ) {
    if ( obj == null ) {
        return obj + "";
    }

    // Support: Android <=2.3 only (functionish RegExp)
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[ toString.call( obj ) ] || "object" :
        typeof obj;
}

//定義對象
var preservedScriptAttributes = {
    type: true,
    src: true,
    noModule: true
};

6.定義完上面的內容後,jQuery內部進行new對象,使得簡化使用操做

var version = "3.3.1",

    //在這裏jquery經過new新生成了對象簡化了使用時的操做
    jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    },

7.new新對象時,jQuery.fn是什麼?

jQuery.fn = jQuery.prototype = {
    jquery: version,
    constructor: jQuery,
    length: 0,
    toArray: function() {
        return slice.call( this );
    },
    get: function( num ) {
        if ( num == null ) {
            return slice.call( this );
        }
        return num < 0 ? this[ num + this.length ] : this[ num ];
    },
    pushStack: function( elems ) {
        var ret = jQuery.merge( this.constructor(), elems );
        ret.prevObject = this;
        return ret;
    },
    each: function( callback ) {
        return jQuery.each( this, callback );
    },
    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();
    },
    push: push,
    sort: arr.sort,
    splice: arr.splice
}

總結:看這部分源碼能夠知道jQuery.fn就是jquery的原型對象

8.jQuery.fn.init( selector, context )具體作了什麼?

//selector 選擇器,多是DOM對象、html字符串、jQuery對象
//context 選擇器選擇的範圍
//rootjQuery == $(document);
init = jQuery.fn.init = function( selector, context, root ) {
        var match, elem;

        // 沒有傳選擇器直接返回
        if ( !selector ) {
            return this;
        }

        root = root || rootjQuery;

        // 選擇器傳入的是字符串
        if ( typeof selector === "string" ) {
            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {
                match = [ null, selector, null ];
        } else {
                match = rquickExpr.exec( selector );
        }

        if ( match && ( match[ 1 ] || !context ) ) {
                // HANDLE: $(html) -> $(array)
                if ( match[ 1 ] ) {
                    context = context instanceof jQuery ? context[ 0 ] : context;
                    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 ( 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 ] );

                    if ( elem ) {

                        // Inject the element directly into the jQuery object
                        this[ 0 ] = elem;
                        this.length = 1;
                    }
                    return this;
                }

            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || root ).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[ 0 ] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( isFunction( selector ) ) {
            return root.ready !== undefined ?
                root.ready( selector ) :

                // Execute immediately if ready is not present
                selector( jQuery );
        }

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

總體看摸不着頭腦,抽離結構以下:

init = jQuery.fn.init = function( selector, context, root ) {
        if ( typeof selector === "string" ) {
            //選擇器類型是字符
            
        }else if(  selector.nodeType  ){
            //選擇器類型是節點
            
        }else if( jQuery.isFunction( selector ) ){
            //簡化$(document).ready(function(){});
            
        }
        
        //返回結果。
        return jQuery.makeArray( selector, this );
        }

抽離完了要想理解這些內容,首先來看看jquery到底支持哪些選擇器selector?

1.$(document)   
2.$('<div>') 
3.$('.div') 
4.$('#test') 
5.$(function(){}) 
6.$("input:radio", document.forms[0]); 
7.$('input', $('div')) 
8.$() 
9.$("<div>", {  "class": "test" }).appendTo("body");

接着一個一個分支的看,它是如何支持這些選擇器的,首先是typeof selector === "string"

if ( typeof selector === "string" ) {
            //傳入的是標籤類型,好比<p>
            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {
                // 將html儲存入match數組中,並與另外一個分支中的正則捕獲相對應
                match = [ null, selector, null ];
            } else {
                //放入正則進行匹配,結果類型是:[全匹配, <tag>, #id]
                //rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/
                //匹配HTML標記和ID表達式
                match = rquickExpr.exec( selector );
            }

            // 若是match不爲空,而且match[1]也就是<tag>存在
            if ( match && ( match[ 1 ] || !context ) ) {
                if ( match[ 1 ] ) {
                    // 若是context是jQuery對象,則取其中的第一個DOM元素做爲context
                    context = context instanceof jQuery ? context[ 0 ] : context;
                    // 將經過parseHTML處理生成的DOM對象merge進jQuery對象
                    jQuery.merge( this, jQuery.parseHTML(
                        match[ 1 ],
                        //若是context存在而且是note節點,則context就是的頂級節點或自身,不然content=document
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            if ( isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                //若是是#id的形式,走這個分支進行處理
                } else {
                    //經過getEle方法得到DOM對象 將match[2]傳入,是由於#id的形式是在第二個捕獲組裏面儲存的。
                    elem = document.getElementById( match[ 2 ] );
                    // 若是該id元素存在
                    if ( elem ) {
                        // 將該元素保存進jQuery對象數組當中,並設置其length值爲1
                        this[ 0 ] = elem;
                        this.length = 1;
                    }
                    return this;
                }

            } else if ( !context || context.jquery ) {
                //若是context不存在或者context是jQuery對象 
                //經過檢測是否是有jquery屬性
                // 進入Sizzle進行處理(複雜的選擇器)
                return ( context || root ).find( selector );

            } else {
                //context存在而且context不是jQuery對象的狀況 先調用$(context),在進入Sizzle進行處理
                return this.constructor( context ).find( selector );
            }

        }

接着是selector.nodeType分支

else if ( selector.nodeType ) {
    //直接將DOM元素存入jQuery對象並設置context和length
    this.context = this[0] = selector;
    this.length = 1;
    return this;
   }

最後是jQuery.isFunction( selector )分支

else if ( jQuery.isFunction( selector ) ) {
        //簡化$(document).ready(function(){});
    return rootjQuery.ready( selector );
  }

分析了以上的分支,把jquery的選擇器分別帶進去走一下流程,首先是`3.$('div')
首先進入:

if ( typeof selector === "string" ) {}

接着進入下面的if分支:

if ( match && (match[1] || !context) ) {
    if ( match[1] ) {
        context = context instanceof jQuery ? context[0] : context;
        jQuery.merge( this, jQuery.parseHTML(
        match[1],
        context && context.nodeType ? context.ownerDocument || context : document,
        true
      ) );
    }
}

它進入了一個函數parseHTML()

jQuery.parseHTML = function( data, context, keepScripts ) {
    if ( typeof data !== "string" ) {
        return [];
    }
    if ( typeof context === "boolean" ) {
        keepScripts = context;
        context = false;
    }

    var base, parsed, scripts;

    if ( !context ) {
        if ( support.createHTMLDocument ) {
            context = document.implementation.createHTMLDocument( "" );
            base = context.createElement( "base" );
            base.href = document.location.href;
            context.head.appendChild( base );
        } else {
            context = document;
        }
    }
    //var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
    //匹配一個獨立的標籤
    parsed = rsingleTag.exec( data );
    scripts = !keepScripts && [];

    if ( parsed ) {
        return [ context.createElement( parsed[ 1 ] ) ];
    }
    //未經過節點的字符串,則經過建立一個div節點,將字符串置入div的innerHTML
    parsed = buildFragment( [ data ], context, scripts );

    if ( scripts && scripts.length ) {
        jQuery( scripts ).remove();
    }

    return jQuery.merge( [], parsed.childNodes );
};

最後返回this也就是jQuery

return this;

再看一下4.$("#id")
首先進入:

if ( typeof selector === "string" ) {}

接着進入下面的else分支進行正則匹配:

if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
    //不知足
}else{
    match = rquickExpr.exec( selector );
}

匹配完了再接着進入下面的else分支進行尋找添加:

if ( match[1] ) {
    //不知足
}else{
    elem = document.getElementById( match[2] );
    if ( elem ) {
   // 將該元素保存進jQuery對象數組當中,並設置其length值爲1
   this.length = 1;
    this[0] = elem;
  }
}

最後返回this也就是jQuery

return this;

今兒先看到這!!!!

相關文章
相關標籤/搜索