摘要:入口結構具體代碼抽離結構如下涉及到的知識解析函數時的規則函數定義和函數表達式閉包解析函數的規則解析器會在遇到時將其認為是函數定義而非函數表達式函數定義和函數表達式函數定義函數表達式閉包閉包函數中的函數,本質是指作用域內的作用域閉包舉例綜合以
1.入口結構
( 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 ) { //具體代碼 }
抽離結構如下:
( function() { })()
涉及到的知識
js解析函數時的規則
函數定義和函數表達式
js閉包
js解析函數的規則
js解析器會在遇到function時將其認為是函數定義而非函數表達式
函數定義和函數表達式
函數定義:function test(){ }
函數表達式:let test = function(){ }
js閉包
閉包:函數中的函數,本質是指作用域內的作用域
//閉包舉例 function f(){ var a = 2; function g(){ console.log(a) }; return g; } f()();
綜合以上的內容,再來看一下剛才抽離出來的代碼
(function(){ })()
第一個括號有兩個作用:
讓js解析器把后面的function當作函數表達式而不是函數定義
形成一個作用域,類似在上面閉包例子中的f函數
第二個括號
觸發函數并傳參
2.第二個括號內的參數有哪些?
(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.$("") 3.$(".div") 4.$("#test") 5.$(function(){}) 6.$("input:radio", document.forms[0]); 7.$("input", $("div")) 8.$() 9.$("", { "class": "test" }).appendTo("body");接著一個一個分支的看,它是如何支持這些選擇器的,首先是typeof selector === "string"
if ( typeof selector === "string" ) { //傳入的是標簽類型,比如if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // 將html儲存入match數組中,并與另一個分支中的正則捕獲相對應 match = [ null, selector, null ]; } else { //放入正則進行匹配,結果類型是:[全匹配,
, #id] //rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]+))$/ //匹配HTML標記和ID表達式 match = rquickExpr.exec( selector ); } // 如果match不為空,并且match[1]也就是 存在 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][^/