jquery源碼分析(三)——工具函數 DOMContentLoaded 與onload區別

jQuery.extend({
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),//生成字符串,使用Math.random生成隨機數並使用正則去掉了非數字字符。
                                            //這裏它做爲HTMLElement或JS對象的屬性名
isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, isWindow: function( obj ) { /* jshint eqeqeq: false */ return obj != null && obj == obj.window; }, isNumeric: function( obj ) { // parseFloat NaNs numeric-cast false positives (null|true|false|"") // ...but misinterprets leading-number strings, particularly hex literals ("0x...") // subtraction forces infinities to NaN return obj - parseFloat( obj ) >= 0; }, isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }, isPlainObject: function( obj ) { var key; if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Support: IE<9 // Handle iteration over inherited properties before own properties. if ( support.ownLast ) { for ( key in obj ) { return hasOwn.call( obj, key ); } } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, type: function( obj ) { if ( obj == null ) { return obj + ""; } return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call(obj) ] || "object" : typeof obj; }, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && jQuery.trim( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }, // args is for internal usage only each: function( obj, callback, args ) { var value, i = 0, length = obj.length, isArray = isArraylike( obj ); if ( args ) { if ( isArray ) { for ( ; i < length; i++ ) { value = callback.apply( obj[ i ], args ); if ( value === false ) { break; } } } else { for ( i in obj ) { value = callback.apply( obj[ i ], args ); if ( value === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isArray ) { for ( ; i < length; i++ ) { value = callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } } else { for ( i in obj ) { value = callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } } } return obj; }, // Use native String.trim function wherever possible trim: trim && !trim.call("\uFEFF\xA0") ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : ( text + "" ).replace( rtrim, "" ); }, // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; if ( arr != null ) { if ( isArraylike( Object(arr) ) ) { jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else { push.call( ret, arr ); } } return ret; }, inArray: function( elem, arr, i ) { var len; if ( arr ) { if ( indexOf ) { return indexOf.call( arr, elem, i ); } len = arr.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays if ( i in arr && arr[ i ] === elem ) { return i; } } } return -1; }, merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; while ( j < len ) { first[ i++ ] = second[ j++ ]; } // Support: IE<9 // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) if ( len !== len ) { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, invert ) { var callbackInverse, matches = [], i = 0, length = elems.length, callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { callbackInverse = !callback( elems[ i ], i ); if ( callbackInverse !== callbackExpect ) { matches.push( elems[ i ] ); } } return matches; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, i = 0, length = elems.length, isArray = isArraylike( elems ), ret = []; // Go through the array, translating each of the items to their new values if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { var args, proxy, tmp; if ( typeof context === "string" ) { tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind args = slice.call( arguments, 2 );// 從參數列表中去掉fn,context proxy = function() {//使用apply修改fn執行上下文環境 return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || jQuery.guid++;//統一guid,使得proxy可以被移除 return proxy; }, now: function() { return +( new Date() ); }, // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support });

(1)、首先來了解下調用Object原型上的方法,toString方法,經過調用該方法能夠返回 "[object XXX]"格式的字符串。jQuery內部就是用過這種方式實現了數據類型判斷:javascript

toString.call(obj) 返回值爲css

參數 返回值
true [object Boolean]
'abcde'  [object String]
123  [object Number]
function(){} [object Function]
new Date() [object Date]
/g/ [object RegExp]
{} [object object]

 

jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});//統一設置經過toString來判斷數據類型對應的返回值 
type: function( obj ) {
        if ( obj == null ) {
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[ toString.call(obj) ] || "object" :
            typeof obj;
    },

  

(2)、看下擴展了那些類型判斷的方法:html

isFunction、isNumeric 、isArray、isEmptyObject、java

isPlainObject:obj不存在 或 非object類型 或 DOM節點 或 widnow對象,具備構造函數constructor,卻不是自身的屬性(即經過prototype繼承的)都返回falsenode

 

(3)、jQuery封裝了幾個經常使用遍歷對象或數組方法:each map grep inArray  makeArray,從應用場景來分析:jquery

  1. grep  ----(篩選出知足條件的數據,返回一個數組)
  2. each  ---- 對數組或對象 進行處理,不返回數據 ,可是仍是能夠經過在回掉函數中返回false 來終止,args用來區分jQuery內部調用該方法仍是外部調用
  3. map  ----對數組或對象 進行數據處理,遍歷數組或對象收集處理每一個item處理後結果(對每個屬性調用callback,將返回值不爲null的值,存入ret返回)。代碼中針對數組和對象作了判斷,但僅僅是遍歷的方式不一樣,處理方式沒有其餘的區別——返回null,則忽略(無返回值的function會返回undefined)。
  4. inArray  ----返回值在數組中的鍵值

(4)、proxy( fn, context ) ——代理方法:爲fn指定上下文(即this)。修改fn使用上下文環境。這在jQuery很經常使用到。web

實際上它是經過apply來實現上下文環境的切換的。數組

proxy = function() {
  return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
};promise

function proxy() 開始判斷context類型是否爲字符串。這樣是處理了fn contex 參數位置互換問題。瀏覽器

 

二、前面分析這麼多,忽然發現把最經常使用的ready忘了,下面來對比下jQuery中ready與load事件。

 jQuery有3種針對文檔加載的方法:
$(document).ready(function() {
    // ...代碼...
})
$(function() {//上面document ready的簡寫
    // ...代碼...
})
$(document).load(function() {
    // ...代碼...
})

因此,實際也就兩個針對文檔加載方法:一個是ready一個是load,那麼兩個到底有什麼區別呢?

(1)、疑問???ready與load誰先執行:答案是ready先執行,load後執行。

(2)、理解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事件就會遲遲得不到觸發,嚴重影響用戶體驗。——後面有一篇專門分析  DOMContentLoaded 與onload區別 

(3)、下面咱們看看jQuery是如何處理文檔加載時機的問題:

// The deferred used on DOM ready
var readyList;//做爲異步回調隊列

jQuery.fn.ready = function( fn ) {
	// Add the callback
	jQuery.ready.promise().done( fn );//使用了狀態機制

	return this;
};

jQuery.extend({
	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
		if ( !document.body ) {
			return setTimeout( jQuery.ready );
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.trigger ) {
			jQuery( document ).trigger("ready").off("ready");
		}
	}
});

/**
 * Clean-up method for dom ready events
 */
function detach() {//清除DOM加載完成事件
	if ( document.addEventListener ) {
		document.removeEventListener( "DOMContentLoaded", completed, false );
		window.removeEventListener( "load", completed, false );

	} else {
		document.detachEvent( "onreadystatechange", completed );
		window.detachEvent( "onload", completed );
	}
}

/**
 * The ready event handler and self cleanup method
 */
function completed() {
	// readyState === "complete" is good enough for us to call the dom ready in oldIE
	if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
		detach();
		jQuery.ready();
	}
}

jQuery.ready.promise = function( obj ) {//定義一個狀態機
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called after the browser event has already occurred.
		// we once tried to use readyState "interactive" here, but it caused issues like the one
		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			setTimeout( jQuery.ready );

		// Standards-based browsers support DOMContentLoaded
		} else if ( document.addEventListener ) {
			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed, false );

		// If IE event model is used
		} else {
			// Ensure firing before onload, maybe late but safe also for iframes
			document.attachEvent( "onreadystatechange", completed );

			// A fallback to window.onload, that will always work
			window.attachEvent( "onload", completed );

			// If IE and not a frame
			// continually check to see if the document is ready
			var 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();
					}
				})();
			}
		}
	}
	return readyList.promise( obj );
};

  

jQuery的ready是經過promise給包裝過的,這也是jQuery擅長的手法,統一了回調體系,在回調deferred中我會重點討論下。
可見jQuery兼容的具體策略:針對高級的瀏覽器,咱們當前很樂意用DOMContentLoaded事件了,省時省力。

那麼舊的IE如何處理呢?繼續看jQuery的方案:

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

相關文章
相關標籤/搜索