1.選擇器結構
jQuery的選擇器根據源碼可以分為幾塊
init: function( selector, context, rootjQuery ) { ... // HANDLE: $(""), $(null), $(undefined), $(false) ... // Handle HTML strings if ( typeof selector === "string" ) { ... // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { ... // HANDLE: $(function) } else if ( jQuery.isFunction( selector ) ) { ... } //HANDLE: $($...) if ( selector.selector !== undefined ) { ... } return jQuery.makeArray( selector, this ); }
可以看到,jQuery接受的參數方式也就這么幾個(""/null/undefined/false)、(string, context, rootjQuery)、(DOMElement)、(function)、($...)。除了第一個參數是string的情況比較復雜以外,其他情況都比較簡單。
$(""/null/undefined/false)直接返回即可
// HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; }
$(DOMElement)將DOM對象轉化為偽數組返回即可
// HANDLE: $(DOMElement) else if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; }
$(function)等同於jQuery.ready(function)
// HANDLE: $(function) } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); }
$($...)將參數執行結果(也是一個jQuery實例)包裝到this上返回
//HANDLE: $($...) if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this );
接下來重點:當jQuery傳遞的第一個參數是字符串的時候
2. jQuery選擇器核心—選擇器是字符串$(String[,xxx])
首先必須要必備的幾個正則知識:
test方法:
RegExpObject.test(string)用於檢測一個字符串是否匹配某個模式。
如果字符串 string 中含有與 RegExpObject 匹配的文本,則返回 true,否則返回 false。
match方法:
stringObject.match(searchvalue | regexp)可在字符串內檢索指定的值,或找到一個或多個正則表達式的匹配。該方法類似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
match方法將檢索字符串 stringObject,以找到一個或多個與 regexp 匹配的文本。這個方法的行為在很大程度上有賴於 regexp 是否具有標志 g。
如果 regexp 沒有標志 g,那么 match() 方法就只能在 stringObject 中執行一次匹配。如果沒有找到任何匹配的文本, match() 將返回 null。否則,它將返回一個數組,其中存放了與它找到的匹配文本有關的信息。該數組的第 0 個元素存放的是匹配文本,而其余的元素存放的是與正則表達式的子表達式匹配的文本。除了這些常規的數組元素之外,返回的數組還含有兩個對象屬性。index 屬性聲明的是匹配文本的起始字符在 stringObject 中的位置,input 屬性聲明的是對 stringObject 的引用。
如果 regexp 具有標志 g,則 match() 方法將執行全局檢索,找到 stringObject 中的所有匹配子字符串。若沒有找到任何匹配的子串,則返回 null。如果找到了一個或多個匹配子串,則返回一個數組。不過全局匹配返回的數組的內容與前者大不相同,它的數組元素中存放的是 stringObject 中所有的匹配子串,而且也沒有 index 屬性或 input 屬性。
注意:在全局檢索模式下,match() 即不提供與子表達式匹配的文本的信息,也不聲明每個匹配子串的位置。如果您需要這些全局檢索的信息,可以使用 RegExp.exec()。
exec方法:
RegExpObject.exec(string) 返回一個數組,其中存放匹配的結果。如果未找到匹配,則返回值為 null。
此數組的第 0 個元素是與正則表達式相匹配的文本,第 1 個元素是與 RegExpObject 的第 1 個子表達式相匹配的文本(如果有的話),第 2 個元素是與 RegExpObject 的第 2 個子表達式相匹配的文本(如果有的話),以此類推。除了數組元素和 length 屬性之外,exec() 方法還返回兩個屬性。index 屬性聲明的是匹配文本的第一個字符的位置。input 屬性則存放的是被檢索的字符串 string。我們可以看得出,在調用非全局的 RegExp 對象的 exec() 方法時,返回的數組與調用方法 String.match() 返回的數組是相同的。
但是,當 RegExpObject 是一個全局正則表達式時,exec() 的行為就稍微復雜一些。它會在 RegExpObject 的 lastIndex 屬性指定的字符處開始檢索字符串 string。當 exec() 找到了與表達式相匹配的文本時,在匹配后,它將把 RegExpObject 的 lastIndex 屬性設置為匹配文本的最后一個字符的下一個位置。這就是說,您可以通過反復調用 exec() 方法來遍歷字符串中的所有匹配文本。當 exec() 再也找不到匹配的文本時,它將返回 null,並把 lastIndex 屬性重置為 0。
重要事項:如果在一個字符串中完成了一次模式匹配之后要開始檢索新的字符串,就必須手動地把 lastIndex 屬性重置為 0。
提示:請注意,無論 RegExpObject 是否是全局模式,exec() 都會把完整的細節添加到它返回的數組中。這就是 exec() 與 String.match() 的不同之處,后者在全局模式下返回的信息要少得多。因此我們可以這么說,在循環中反復地調用 exec() 方法是唯一一種獲得全局模式的完整模式匹配信息的方法。
eg:
var str = "Visit W3School sfsffs test W3School W3School"; var patt = new RegExp(/(W3)(School)/); var result = patt.exec(str);// ["W3School", "W3", "School"] result.index;//6 result.input;// "Visit W3School sfsffs test W3School W3School" patt = new RegExp(/(W3)(School)/g); result = patt.exec(str);// ["W3School", "W3", "School"] result.index;//6 patt.lastIndex;//14 result = patt.exec(str);// ["W3School", "W3", "School"] result.index;//27 patt.lastIndex;//35
jQuery選擇器第一個參數為字符串的源碼分析
注:stringObject.charAt(index)方法可返回指定位置的字符
先整體瀏覽源碼在細細分析
var match, elem;
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // 匹配html標簽 match = [ null, selector, null ]; } else { //rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, //匹配html標簽+字符串(可以是空字符)或#+字符串(可以是空字符) match = rquickExpr.exec( selector ); } //匹配的html或確保沒有為選擇器為#id的情況下指定上下文 //因為當選擇器為#id的情況下,match = ["#id", undefined, "id"],match[1]是undefined if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array);選擇器是html字符串,將字符串解析成DOM元素數組返回 if ( match[1] ) { ... } // HANDLE: $(#id);選擇器是ID else { ... } // HANDLE: $(expr, $(...));選擇器的context沒有或是jQuery對象的情況,比如$("p")、$("p",$(".test")):查找$(".test")下的p標簽元素 } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector );
// HANDLE: $(expr, context);選擇器的context為真實的上下文環境,比如$("p",".test"):查找class .test下的p標簽元素,等價於$(context).find(expr) } else { return this.constructor( context ).find( selector ); }
其中使用到find函數我們會在后面去解析他,現在我們只需要知道他是用來查找下級節點即可。接下來主要分析選擇器為id和html字符串的情況
a.選擇器為html字符串
首先將html字符串解析成DOM節點集合,然后將它和當前jQuery實例合並
jQuery.merge( this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true ) );
比如:$("<span><p></p></span><div></div>")合並結果為
然后判斷html字符串是否是一個單獨的標簽並且后一個參數是否是對象(props對象)。如果滿足條件,將對象的屬性(property)添加到標簽屬性(attribute)上。特別需要注意的是如果props對象的某個屬性名稱(假設為fncName)是剛才搜集的DOM集合對象(實際上就是jQuery對象)的函數名的情況下,將props對象的fncName屬性的值作為參數傳遞給DOM集合對象中的fncName函數並執行之
//rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/ // HANDLE: $(html, props) if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // context的屬性是函數的話執行之 if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); //其他情況設置t as attributes } else { this.attr( match, context[ match ] ); } } }
最終返回處理過的DOM集合對象
return this;
完整源碼
// 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 ) { if ( jQuery.isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); }else { this.attr( match, context[ match ] ); } } } return this; }
b.選擇器為ID
如果選擇器為ID,則直接使用原生的document.getElementById直接獲取即可。
唯一需要做的是兼容為難題。Blackberry 4.6返回的節點可能已經不再DOM上了(緩存導致),所以需要判斷該節點是否存在並且其父節點也存在;
if ( elem && elem.parentNode )
IE和Opera有時候會使用name屬性代替ID屬性,返回的結果可能不是我們期望的,這個時候需要做兼容。使用find查找來替代
if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); }
將兼容后得到的節點元素添加到this上並返回。
完整源碼:
// HANDLE: $(#id) else { elem = document.getElementById( match[2] ); // Blackberry 4.6 返回的節點可能已經不再文檔中#6963 if ( elem && elem.parentNode ) { // IE 和Opera有時使用name替代ID去查詢導致結果不對的處理 if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; }