jQuery建構函式init參數分析續
其實樓主的F和jQuery.fn.init是相等的; 實現功能是和jq一樣的, 只是jq的把建構函式放進原型;如果非要說原因,個人理解jq這樣寫整體結構清晰,先是入口建構函式,緊跟著是原型部分(原型裡面init是初始化),但是不好理解;乍一看確實挺繞, 我也是看了好久才明白怎麼回事
如果selector是其他字串情況就比較多了比較複雜了
?
1 2 |
// Handle HTML strings if ( typeof selector === "string" ) {...} |
開始分不同的情況處理
?
1 2 3 4 5 6 7 |
// Are we dealing with HTML string or an ID? 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 = quickExpr.exec( selector ); } |
If裡面先判斷第一個字元是“<”最後一個字元是“>”並且長度大於3就假設此時的selector是html簡單標籤 ,比如$(‘
')但是記住僅僅是假設”assume”比如$(‘')這樣的也會走這裡。然後把match數組修改成[null,selector,null],這裡的match是在init函數裡面聲明的變數,主要是用來作為區分是參數類型的工具稍後在將可能情況列出,下面是源碼中聲明的四個變數
?
1 2 |
init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; |
如果不滿足if的條件就會調用一個正則去得到match的結果,quickExpr是jQuery建構函式裡面聲明的變數
?
1 2 3 |
// A simple way to check for HTML strings or ID strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, |
這個正則主要是為了區別html字串和id字串的,第二個注釋中講到了為了避免基於 location.hash的 XSS 攻擊,於是在 quickExpr 中增加了 #(#9521)的意思是我們可以在jQuery官網找到相關解釋。
首先訪問http://bugs.jquery.com/然後搜尋對應的值即可
quickExpr.exec( selector )執行的結果可以是一個數組,數組的第一個元素是匹配的元素,剩下的分別是分組匹配的元素,這個正則有兩個分組(<[\w\W]+>)[^>]和([\w\-]*)一個是標籤一個是id值。最終會把結果交給match。下面就來分析下match的各種情況首先單標籤不用正則式是 [ null, selector, null ]的形式,下面在代碼中證明:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!doctype html> <html> <head> <title></title> <script src='jquery-1.7.1.js'></script> </head> <body> <div id='div'></div> </body> <script> $('<div>'); </script> </html> |
在html裡面我們建立一個jQuery對象然後再init方法裡面輸出得到的match結果:
?
1 2 3 4 5 6 7 |
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 = quickExpr.exec( selector ); } console.log(match); // [null, "<div>", null]; |
下面我們修改一下參數改為$(‘#div')然後再看一下結果
代碼如下:
["#div", undefined, "div", index: 0, input: "#div"]
還有一種比較特殊的情況$(‘
123')然後我們再看一下結果
代碼如下:
["
dewrwe", "
", undefined, index: 0, input: "
dewrwe"]
我們可以看到id總是在第三個元素而標籤值在第二個元素儲存著,對於最後一種情況而言跟$(‘
')是沒有什麼區別的因為產生dom元素時是不會處理第一個元素的。基於這個結果可以接著來分析下一個判斷了。
接下來的會根據match的結果分為三種情況
?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if ( match && (match[1] || !context) ) { ... } else if ( !context || context.jquery ) { ... } else { ... } |
第一種情況滿足的條件是match一定要有值,match[1]就是第二個元素就是儲存標籤的這個有值或者不存在上下文,但是好像沒有id什麼事啊?其實不是的通過分析match的結果可以知道第二個元素沒有值肯定就是id選取器得到的結果,而id是唯一的,不需要寫上下文(其實寫了上下文也會正常執行只不過會使用Sizzle而不是在這裡處理了跟body是一樣的)。好了第一個條件進來的情況就是
1.標籤
$(‘
') $(‘
123') $(‘
23213213
')...
2.沒有內容相關的id $(‘#div')
第一個條件內部又進行了細分:
?
1 2 3 4 5 6 7 8 9 10 |
// HANDLE: $(html) -> $(array) if ( match[1] ) { ... // HANDLE: $("#id") }else{ } |
很顯然if是處理標籤的else是處理id的,先來看看是怎麼處理標籤的吧
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector); |
首先修正一下context的值,如果是jQuery對象就把他變成dom元素就是使用下標的方法這個原理之前說過了,然後有處理了doc變數,如果context不存在就把document賦值給doc如果存在且有ownerDocument屬性那就是dom元素了這個值還是document如果不是dom元素比如普通的js對象的話那就把這個對象賦值給doc變數。緊接著對selector又進行了一個正則判斷,這個正則也是在jQuery建構函式裡面聲明的目的是判斷單標籤 比如
這樣的
代碼如下:
// Match a standalone tag
rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
然後把結果交給ret變數,基於ret的值又進行劃分按照單標籤和複雜標籤分開處理ret值存在那就是匹配到了單標籤然後再根據context是不是普通對象又分為兩種情況isPlainObject是檢測是不是普通對象的方法,如果是普通對象,就利用js原生方法createElement傳入標籤建立元素並放在一個數組裡面,之所以這樣是為了以後跟jquery對象合并方便,然後把數組賦值給selector,後採用對象冒充的方法調用attr方法,這裡attr居然有3個參數,而平常我們使用的api裡面是兩個參數,其實jQuery中有很多類似的情況,同樣的方法有著對內對外兩個介面。第二個參數就是對象形式的上下文,因為attr可以像
代碼如下:
$("img").attr({ src: "test.jpg", alt: "Test Image" });
這給我們的其實就是我們以後可以$(‘
',{id:'div'})這樣寫了也是支援的。如果不是對象就直接建立元素不考慮屬性。還是把建立的元素放在數組裡面。如果ret沒有值那就是複雜的標籤了比如$(‘
231
')這樣的這個時候原生的js就搞不定啦需要調取另外一個方法jQuery.buildFragment來處理,這個方法實現以後在學習吧,總之最後都會建立dom元素。最後返回合并後的結果
代碼如下:
return jQuery.merge( this, selector );
不像之前的return this這裡是返回merge執行後的結果其實他的任務就是把放在數組裡面的建立好的的dom元素合并到jquery元素中去,最終變成{0:div,length:1...}這樣的對象形式。這樣的話簡標籤情況就處理完畢。
然後else裡面處理的是id的情況
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; |
很簡單直接調用原生js的id選取器但是有一些系統會出現bug
注釋說的很清楚黑莓系統,就是元素已經不存在了但是依然能夠匹配得到所以再加上父節點,不存在的元素肯定沒有父節點的。還有一種情況就是ie和opera瀏覽器會出現按name值匹配的情況所以在做了一個判斷
if ( elem.id !== match[2] ) {
如果真的不幸出現了那就不能使用原生方法而是用find方法也就是使用sizzle引擎了,在大多數正常情況下就直接將擷取到的元素放到this裡面就可以啦然後修改下context的值。Ok終於把第一個大分支分析完了。然後再看根據match的第二個分支
?
1 2 3 |
else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); } |
這裡是如果沒有上下文或者上下文是jquery對象的時候這個比較簡單就是直接用find方法了rootjQuery 就是$(document)
最後字串的情況上面都不屬於的話
代碼如下:
return this.constructor( context ).find( selector );
This.constructor就是jQuery其實還是使用find方法。
以上所述就是本文的全部內容了,希望大家能夠喜歡。