看書時注意到下面兩條語句的功效是相同的,
- $(function(){alert("hello!");});
- $(document).ready(function(){alert("hello!");});
這個特殊寫法就是用$()代替$(document).ready(),類似于(有差異)window.onload彈出個窗口:
查看jQuery1.8.3源代碼,是這樣封裝的:
- (function( window, undefined ) {
- /*...jQuery源代碼全部都在這里了...*/
- })( window );
下列語句把封裝在內部的jQuery先賦給window.$,緊接著再賦給window.jQuery。這意味著在實際使用時window.$和window.jQuery是一回事。因為$這個符號只有1個字母,比jQuery短,所以更常用一些,但要注意到$非jQuery所獨有,節約字母的代價是增加了命名沖突的風險。
- // Expose jQuery to the global object
- window.jQuery = window.$ = jQuery;
下面是jQuery的初始化語句(注意到此時函數并未執行):
- // Define a local copy of jQuery
- jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- }
找到jQuery.fn的定義,這是一個對象,其中有一個叫init的函數元素:
- jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
- // Handle $(""), $(null), $(undefined), $(false)
- if ( !selector ) {
- return this;
- }
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
- /*...以下省略...*/
繼續下去,init中有一段邏輯:
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
暈了暈了,rootjQuery的定義又回到了jQuery:
- // All jQuery objects should point back to these
- rootjQuery = jQuery(document);
有點遞歸的意思了,嗯,就是遞歸。jQuery不僅僅是一個函數,而且還是一個遞歸函數。
如果調用jQuery時輸入的是一個函數,例如文章開頭提到的:
- $(function(){alert("hello!");});
那么這個函數就會走到rootjQuery那里,再回到jQuery,執行jQuery(document).ready。而$與jQuery是一回事,這樣就解釋了$(inputFunction)可以代替$(document).ready(inputFunction)。
現在還不想結束此文,我的問題是$(document)做了什么?嗯,還是要進入到jQuery.fn.init,確認存在nodeType屬性,達到“Handle $(DOMElement)”的目的。怎么Handle呢?具體就是把輸入參數(此時為document)賦值給this的context屬性,然后再返回this。也就是說,$(document)執行完了返回的還是jQuery,但是情況發生了變化,具體就是context屬性指向了輸入參數(此時為document)。暫時還不明白繞這么大個圈子為context(上下文)屬性賦值有何意義?
接下去的問題可能會是$(document).ready和window.onload的區別?提取ready函數的定義如下:
- ready: function( fn ) {
- // Add the callback
- jQuery.ready.promise().done( fn );
- return this;
- },
閱讀代碼探究promise是有點暈啊,想到自己的iJs工具包了,打印jQuery.ready.promise()如下:
[Object] jQuery.ready.promise()
|--[function] always
|--[function] done
|--[function] fail
|--[function] pipe
|--[function] progress
|--[function] promise
|--[function] state
|--[function] then
進一步打印整理done函數代碼如下(這下徹底暈了~~):
- function() {
- if ( list ) {
- // First, we save the current length
- var start = list.length;
- (function add( args ) {
- jQuery.each( args, function( _, arg ) {
- var type = jQuery.type( arg );
- if ( type === "function" ) {
- if ( !options.unique || !self.has( arg ) ) { list.push( arg ); }
- } else if ( arg && arg.length && type !== "string" ) {
- // Inspect recursively add( arg );
- }
- });
- })( arguments );
- // Do we need to add the callbacks to the
- // current firing batch?
- if ( firing ) {
- firingLength = list.length;
- // With memory, if we're not firing then
- // we should call right away
- } else if ( memory ) {
- firingStart = start;
- fire( memory );
- }
- }
- return this;
- }
好在代碼不長,看起來關鍵就在于fire函數了。嗯,找回一絲清醒了。在上面的done函數里面可以注意到使用了默認的arguments變量,將注入的函數push到了list數組。下面是fire函數:
- fire = function( data ) {
- memory = options.memory && data;
- fired = true;
- firingIndex = firingStart || 0;
- firingStart = 0;
- firingLength = list.length;
- firing = true;
- for ( ; list && firingIndex < firingLength; firingIndex++ ) {
- if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
- memory = false; // To prevent further calls using add
- break;
- }
- }
- firing = false;
- if ( list ) {
- if ( stack ) {
- if ( stack.length ) {
- fire( stack.shift() );
- }
- } else if ( memory ) {
- list = [];
- } else {
- self.disable();
- }
- }
- }
可以看到代碼中對list數組里面使用了apply。用iJs包調試可發現data[0]就是document對象,也就是說,調用$(myFunction)的結果是在document對象上執行了myFunction。因為list是個數組,所以也就不難理解$()其實是多次輸入,一次執行。
最后,回過頭來閱讀promise源代碼,關于$()輸入函數的執行時機的秘密就在這里了:
- 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, 1 );
- // Standards-based browsers support DOMContentLoaded
- } else if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- // A fallback to window.
- window.addEventListener( "load", jQuery.ready, false );
- // If IE event model is used
- } else {
- // Ensure firing before
- document.attachEvent( "onreadystatechange", DOMContentLoaded );
- // A fallback to window.
- window.attachEvent( "onload", jQuery.ready );
- // 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 );
- }
- // and execute any waiting functions
- jQuery.ready();
- }
- })();
- }
- }
- }
- return readyList.promise( obj );
- };
從代碼的注釋中可以看到這段代碼在消除bug的過程中還是頗費了些心思的。查看其中一個網址http://bugs.jquery.com/ticket/12282#comment:15,是關于IE9/10的一個bug(document ready is fired too early on IE 9/10),好在已經解決。
繞了這么多彎子,整個事情看起來就是這樣,如果每一個瀏覽器都能有document.readyState === "complete",就簡單了。再看到$(),要感謝編寫jQuery的大神們(以及其他類似框架的大神們),是他們的努力,讓世界變得完美。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。