本文還可在github上查看,可點擊這里~~~~
jQuery源碼-美元$的若干種使用方法
學習jQuery源碼,第一步是了解jQuery整體核心代碼結構。第二步,當然就是了解無比強大無所不能的美元$。根據平常使用jQuery的經驗,你會發現,幾乎所有的語句都是以美元開頭的,比如:
$(function(){ console.log('dom ready 啦!!'); });
又比如:
$('#casper').addClass('handsome');
當然還有其他。。。翻開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.init
老規矩,打開編輯器,定位到jQuery.fn.init這個方法。如果是用sublime的話,可以試下ctrl+r,然后輸入init,第一個出來的搜索結果就是。
相信很多童鞋跟我的第一反應是:oh my god!將近90行代碼!不過還可以接受啦,工作中還見別人寫過300++行的方法,想想90行也算不得可怕。
然而,當你再往下看,可能就會有種想死的心——怎么這么多if、else!
不賣關子之所以會有那么多if、else,是因為——$有將近10種用法,文章最開頭列舉的不過是最常見的兩種用法而已。
以下為jQuery.fn.init的源碼,瞄一眼感受下這代碼的可怕就可以了,可以暫時忽略其中的實現細節,安心進入下一節。

init: function( selector, context, rootjQuery ) { 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 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 rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); },
$的n種用法
上面提到,$的用法有將近10種,也就是說,jQuery.fn.init這一個函數需要處理的情況有將近10種。那究竟都是哪些情況呢?如果想從它的源碼直接看出來的話,那最好放棄。當然並不是說此路不通,只不過有更好的方法而已。
jQuery之所以這么受歡迎,其中一個原因是它的文檔很齊全,這個時候果斷可以去看它的API文檔,請猛擊這里
jQuery的API文檔里面很詳細地將各種情況都列了出來,可以看到,里面共列舉了三大種、八小種情況。至於每種情況的作用、參數、返回值,可自行查看API說明,這里不贅述。
jQuery( selector [, context ] )
jQuery( selector [, context ] )
jQuery( element )
jQuery( elementArray )
jQuery( object )
jQuery( jQuery object )
jQuery()
jQuery( html [, ownerDocument ] )
jQuery( html [, ownerDocument ] )
jQuery( html, attributes )
jQuery( callback )
jQuery( callback )
$的n種用法——更直觀的例子
上面我們已經將$的n種用法非常詳細地列舉出來了,但這只是第一步,因為對着jQuery.fn.init錯綜復雜的邏輯分支,你有可能依舊手忙腳亂,不知如何下手。里面比較明顯能夠看出來的是下面這幾種情況:
jQuery()
jQuery( element )
jQuery( callback )
除了上面這三種情況外,其他五種情況依舊無法在代碼里直觀地看出來。那么腫么辦呢?其實我也沒有特別好的方法,但可以將自己的經驗分享一下,分兩步:
- 把jQuery的API文檔詳細地過一遍,了解$的多種用法以及細節,可點擊這里
- 斷點調試,針對上面列出的幾種情況,編寫最簡單的代碼試例,然后斷點進去看看都跑到了哪些邏輯分支
斷點用例
好,於是我們開始編寫用例,下面提到的用例都基於下面的html片段
<div id="id_container" class="container"> <div class="header"></div> </div>
直接上具體的用例。對於這些用例的具體分析會在下面再講到。
// $('#id_container') $('.container') // $('.header', $('#id_container')[0]) $('.header', $('#id_container')) // $(document.getElementsByTagName('div')) $(document.getElementsByTagName('div')[0]) $($('.header')) $({name:'casper', age:25}) // $('<div class="content"><span>casper</span></div>') $('<div class="content"><span>casper</span></div>', document) $('<div></div>', {'class':'content'}) $('<div/>', {'class':'content'}) // $(function(){ console.log('$(callback)'); });
jQuery()
灰常簡單,直接返回this(jQuery對象)
init: function( selector, context, rootjQuery ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; }
$(callback)
這個很簡單,直接跑進下面這個分支然后就return了
else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); }
jQuery( element )
同樣很簡單,跑到這個分支里去了
else if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; }
jQuery( elementArray )
這個比較費解,似乎前面所有的if、else都不符合,沒錯,其實只有下面這么句話
return jQuery.makeArray( selector, this );
jQuery( jQuery object )
跳過代碼細節,先了解下面的背景知識,看下面的代碼。對於$(selector)返回的jQuery對象,上面都會附加一個selector屬性,作用不介紹。
$('.header').selector
於是乎,華麗麗地跑進下面這個分支,其實作用就是:創建一個jQuery對象,並將參數jQuery對象里的dom節點拷貝到新創建的jQuery對象里
if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this );
jQuery( selector [, context ] )
這個的話,情況比較多,分開講
jQuery('#casper')
init: function( selector, context, rootjQuery ) { //各種省略 } else { //先跑到這個分支里去鳥 match = rquickExpr.exec( selector ); //這里,match==['#casper', undefined, 'casper'] } //各種省略 // 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, $(...)) } // 下面全部省略
jQuery('#casper', docuemnt)
//首先進入這個分支 match = rquickExpr.exec( selector ); //['#casper', undefined, 'casper'] //然后進入這個分支 } else { return this.constructor( context ).find( selector ); }
jQuery('.header')
//先進入這個分支 match = rquickExpr.exec( selector ); // match==null //然后進入這個分支~ else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) }
jQuery('.header', document)
//首先進入這里 match = rquickExpr.exec( selector ); // match==null //然后進入這里 // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); }
jQuery('.header', $('#id_container'))
//首先進入這里 match = rquickExpr.exec( selector ); // match==null //然后進入這里 // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector );
jQuery(html, attribute)
具體例子jQuery('<div></div>', {style: 'background:red;'}),或者jQuery('<div/>', {style: 'background:red'})
//首先進入這里 // 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 ]; //[null, '<div></div>', null] } //然后進入這里 // 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; // context == {style:{background:red}} //先把創建好的dom借點復制到this里 // scripts is true for back-compat jQuery.merge( this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true ) ); //然后將{style: 'background:red'}等屬性添加到創建好的dom節點上 // 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) }
jQuery('<div><span>casper</span></div>')
這個應該是我們經常用到的。。。
//首先華麗麗進入這個分支 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 ]; } //然后進入這個分支 // 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 jQuery.merge( this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // 木有賦值 return this;
結束語
好了,萬惡的美元$就先介紹到這里,第二部的源碼詳解本來還想把代碼路徑全部標出來,這樣更方便觀眾圍觀。不過markdown給改變代碼的字體顏色不知道咋整將就吧,且聽下回分解