<b>2.5 jquery.clean( elems,</b>
context, fragment, scripts )
<b>2.5.1 實作原理</b>
方法jquery.clean( elems, context, fragment, scripts )負責把html代碼轉換成dom元素,并提取其中的script元素。該方法先建立一個臨時的div元素,并将其插入一個安全文檔片段中,然後把html代碼指派給div元素的innerhtml屬性,浏覽器會自動生成dom元素,最後解析div元素的子元素得到轉換後的dom元素。
安全文檔片段指能正确渲染html5元素的文檔片段,通過在文檔片段上建立 html5元素,可以教會浏覽器正确地渲染html5元素,稍後的源碼分析會介紹其實作過程。
如果html代碼中含有需要包裹在父标簽中的子标簽,例如,子标簽<option>需要包裹在父标簽<select>中,方法jquery.clean()會先在html代碼的前後加上父标簽和關閉标簽,在設定臨時div元素的innerhtml屬性生成dom元素後,再層層剝去包裹的父元素,取出html代碼對應的dom元素。
如果html代碼中含有<script>标簽,為了能執行 <script>标簽所包含的javascript代碼或引用的javascript檔案,在設定臨時div元素的innerhtml屬性生成dom元素後,方法jquery.clean()會提取其中的script元素放入數組scripts。注意,将含有<script>标簽的html代碼設定給某個元素的innerhtml屬性後,<script>标簽所包含的javascript代碼不會自動執行,所引用的javascript檔案也不會加載和執行。在11.2.1節分析dom操作的核心工具方法jquery.fn.dommanip()時會看到,在生成的dom元素插入文檔樹後,數組scripts中的script元素會被逐個手動執行。
2.5.2 源碼分析
方法jquery.clean( elems, context, fragment, scripts )執行的8個關鍵步驟如下:
1)建立一個臨時div元素,并插入一個安全文檔片段中。
2)為html代碼包裹必要的父标簽,然後指派給臨時div元素的innerhtml屬性,進而将html代碼轉換為dom元素,之後再層層剝去包裹的父元素,得到轉換後的dom元素。
3)移除ie 6/7自動插入的空tbody元素,插入ie 6/7/8自動剔除的前導空白符。
4)取到轉換後的dom元素集合。
5)在ie 6/7中修正複選框和單選按鈕的選中狀态。
6)合并轉換後的dom元素。
7)如果傳入了文檔片段fragment,則提取所有合法的script元素存入數組scripts,并把其他元素插入文檔片段fragment。
8)最後傳回轉換後的dom元素數組。
下面來看看該方法的源碼實作。
1.?定義jquery.clean(
elems, context, fragment, scripts )
相關代碼如下所示:
6256
clean: function( elems, context, fragment, scripts ) {
第6256行:定義方法jquery.clean( elems, context, fragment, scripts ),它接受4個參數:
參數elems:數組,包含了待轉換的html代碼。
參數context:文檔對象,該參數在方法jquery.buildfragment()中被修正為正确的文檔對象(變量doc),稍後會調用它的方法createtextnode()建立文本節點、調用方法createelement()建立臨時div元素。
參數fragment:文檔片段,作為存放轉換後的dom元素的占位符,該參數在jquery.buildfragment()中被建立。
參數scripts:數組,用于存放轉換後的dom元素中的script元素。
2.?修正文檔對象context
6257
var checkscripttype;
6258
6259
context = context || document;
6260
6261
// !context.createelement fails in ie with an error but returns typeof
'object'
6262
if ( typeof context.createelement === "undefined" ) {
6263 context = context.ownerdocument ||
context[0] && context[0].owner document || document;
6264
}
6265
第6258~6264行:修正文檔對象context,與方法jquery.buildfragment() 對文檔對象doc 的修正類似,ownerdocument
表示了 dom 元素所在的文檔對象。如果文檔對象context 沒有 createelement 方法,則嘗試讀取context.ownerdocument或context[0].ownerdocument,如果都沒有,預設為目前文檔對象
document。
既然方法jquery.buildfragment()已經謹慎地修正了文檔對象doc,并傳給了方法jquery.clean(),那麼這裡為什麼要再次做類似的修正呢?這是為了友善直接調用jquery.clean()轉換html代碼為dom元素。例如,在dom操作子產品的方法.before()和.after()中,将直接調用jquery.clean()轉換html代碼為dom元素,且隻傳入了待轉換的html代碼數組elems,而沒有傳入文檔對象context、文檔片段fragment和script元素數組scripts,相關代碼如下所示:
// jquery.fn.before()
5776
before: function() {
5782 var set = jquery.clean( arguments );
// jquery.fn.after()
5788
after: function() {
5795 set.push.apply( set,
jquery.clean(arguments) );
方法jquery.fn.before()在每個比對元素之前插入指定的節點,方法jquery.fn.after()則在每個比對的元素之後插入指定的節點,這兩個方法将在11.2.4節和11.2.5節介紹和分析。
3.?周遊待轉換的html代碼數組elems
6266
var ret = [], j;
6267
6268
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
6269 if ( typeof elem ===
"number" ) {
6270 elem += "";
6271 }
6272
6273 if ( !elem ) {
6274 continue;
6275 }
6276
6277 // convert html string into dom
nodes
6278 if ( typeof elem === “string” ) {
第6266行:數組ret用于存放轉換後的dom元素。
第6268行:開始周遊待轉換的html代碼數組。
注意,這裡有個減少代碼量的小技巧,在for語句的第1部分,聲明了循環變量elem,在for語句的第2部分取出elems[i]指派給elem,并判斷elem的有效性。傳統的做法可能是比較循環變量i與elems.length,然後在for循環體中把elems[i]指派給elem,再判斷elem的有效性。但這裡通過一條for語句完成了循環變量elem的定義、指派和有效性判斷,減少了代碼量,很實用且不影響閱讀,讀者可以在自己的代碼中嘗試應用這種寫法。
另外,判斷elem的有效性時使用的是“!=”,這樣可以同時過濾null和undefined,卻又不會過濾整型數字0。
第6269~6271行:如果elem是數值型,通過讓elem自加一個空字元串,把elem轉換為字元串,這也是一個很實用的小技巧。後面第6280行的context.createtextnode()也可以支援參數為數值型,這裡把數值型轉換為字元串,是為了簡化随後對elem有效性和類型的判斷。
第6273行:如果!elem為true,即elem可以轉換為false,那麼跳過本次循環,執行下一次循環。這行代碼用于過濾空字元串的情況。如果elem是整型數字0,因為在前面的代碼中已經被轉換成了字元串“0”,是以這裡可以簡單地判斷!elem。
在上面的兩個if語句中,即使if語句塊中隻有一行代碼,仍然用花括号包裹起來,這是一個好的編碼習慣,友善閱讀和了解,避免潛在的錯誤。
第6278行:如果elem是字元串,即html代碼,則開始轉換html代碼為dom元素,這之後的if語句塊是jquery.clean()的核心代碼。
(1)建立文本節點
如果html代碼中不包含标簽、字元代碼和數字代碼,則調用原生方法document.createtextnode()建立文本節點,相關代碼如下所示:
6279 if ( !rhtml.test( elem ) ) {
6280 elem =
context.createtextnode( elem );
6281 } else {
第6279行:用正則rhtml檢測html代碼中是否含有标簽、字元代碼或數字代碼,該正則的定義代碼如下:
5649
rhtml = /<|&#?\w+;/,
标簽的特征字元是左尖括号“<”,字元代碼的特征字元是“&”,數字代碼的特征字元是“&#”。字元代碼和數字代碼是特殊符号的兩種形式,常見的特殊符号與字元代碼、數字代碼的編碼對照表如表2-3所示。
表2-3 常見特殊符号與字元代碼、數字代碼的編碼對照表
特
殊 符 号 字
符 代 碼 數
字 代 碼 備 注
" &quot; &#34; 雙引号
' &apos; &#39; 單引号
< &lt; &#60; 小于
> &#62; &gt; 大于
& &amp; &#38;
&nbsp; &#160; 空格
? &copy; &#169; 版權符号
? &reg; &#174; 注冊符号
? &trade; &#8482; 商标符号
完整的特殊符号編碼對照表請通路:
http://www.w3.org/markup/guide/advanced.html
http://www.w3schools.com/tags/ref_entities.asp
第6280行:原生方法document.createtextnode()用于建立文本節點,但是對于傳給它的字元串參數不會做轉義解析,也就是說,該方法不能正确地解析和建立包含了字元代碼或數字代碼的字元串,而浏覽器的innerhtml機制則可以。例如,下面的代碼将在頁面中輸出“?&copy”:
document.body.innerhtml = '&copy;';
// 顯示轉義後的"@"
document.body.appendchild(
document.createtextnode('&copy;') );
// 顯示原始字元串"&copy;"
第6281行:從這行代碼開始轉換包含了标簽、字元代碼或數字代碼的html代碼。
(2)修正自關閉标簽
6282 // fix
"xhtml"-style tags in all browsers
6283 elem =
elem.replace(rxhtmltag, "<$1></$2>");
6284
第6282~6283行:用正則rxhtmltag比對html代碼中的自關閉标簽,并通過方法replace()替換為成對的标簽,例如,<div/>會被修正為<div></div>。自關閉标簽是指沒有對應的關閉标簽,而是在标簽的最後加一個"/"來關閉它,例如,<div/>。方法replace()的第二個參數中的$1和$2分别對應正則rxhtmltag的第一個和第二個分組。
正則rxhtmltag是修正自關閉标簽的關鍵所在,它的定義代碼如下:
5646
rxhtmltag =
/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
這個正則有些長,可以先用幾個例子來測試一下它的功能。
例1 最簡單的自關閉标簽。
'<div/>'.replace( rxhtmltag,
'<$1></$2>' );
// 輸出:<div></div>
例2 帶有屬性的自關閉标簽。
'<div
class="abc"/>'.replace( rxhtmltag, '<$1></$2>' );
// 輸出:<div
class="abc"></div>
例3 多個标簽組合、标簽前後有其他字元、大寫标簽<a></a>、不需要關閉的标簽<input>、多行标簽\n。
'a<div/>a \t b<a/>b \n c<xyz/>c
\n d<img/>d \r\n e<input>e'.replace( rxhtmltag,
// 輸出:"a<div></div>a
\t b<a></a>b \n c<xyz></xyz>c \n d<img/>d \r\n
e<input>e"
現在來看看正則rxhtmltag的精髓之處。
首先過濾掉不需要修正的标簽:(?!area|br|col|embed|hr|img|input|link|meta|param)。(?!p)是反前向聲明,要求接下來的字元不與模式p比對。如果html代碼中出現了這些标簽,則不做任何處理。例如:
'<areadiv/>'.replace( rxhtmltag,
'<$1></$2>' )
// 輸出:<areadiv/>
然後是精妙的嵌套分組,巧妙地提取了标簽,同時又保留了屬性:<和/>包圍的(([\w:]+)[^>]*)作為第一個分組,其中包含了标簽和屬性;嵌套的([\w:]+)作為第二個分組,其中隻包含了标簽。
關于正規表達式和方法replace()的基礎知識,不熟悉的讀者請查閱相關的書籍資料。
(3)建立一個臨時div元素
6285 // trim whitespace,
otherwise indexof won't work as expected
6286 var tag = ( rtagname.exec(
elem ) || ["", ""] )[1].tolowercase(),
6287 wrap = wrapmap[ tag ]
|| wrapmap._default,
6288 depth = wrap[0],
6289 div =
context.createelement("div");
6290
第6286行:提取html代碼中的标簽部分,删除了前導空白符和左尖括号,并轉換為小寫指派給變量wrap。正則rtagname的定義如下:
5647
rtagname = /<([\w:]+)/,
第6287行:從對象wrapmap中取出标簽tag對應的父标簽,它的定義和初始化代碼如下:
5657
wrapmap = {
5658
option: [ 1, "<select multiple='multiple'>",
"</select>" ],
5659
legend: [ 1, "<fieldset>", "</fieldset>"
],
5660
thead: [ 1, "<table>", "</table>" ],
5661
tr: [ 2, "<table><tbody>", "</tbody></table>"
5662
td: [ 3, "<table><tbody><tr>",
"</tr></tbody></table>" ],
5663
col: [ 2,
"<table><tbody></tbody><colgroup>",
"</colgroup></table>" ],
5664
area: [ 1, "<map>", "</map>" ],
5665
_default: [ 0, "", "" ]
5666
},
5667
safefragment = createsafefragment( document );
5668
5669 wrapmap.optgroup =
wrapmap.option;
5670 wrapmap.tbody =
wrapmap.tfoot = wrapmap.colgroup = wrapmap.caption = wrapmap.thead;
5671 wrapmap.th =
wrapmap.td;
5672
5673 // ie can't serialize
<link> and <script> tags normally
5674 if (
!jquery.support.htmlserialize ) {
5675
wrapmap._default = [ 1, "div<div>",
"</div>" ];
5676 }
在對象wrapmap中包含了以下标簽:option、optgroup、legend、thead、tbody、tfoot、colgroup、caption、tr、td、th、col、area、_default,每個标簽對應一個數組,數組中的元素依次是:包裹的深度、包裹的父标簽、父标簽對應的關閉标簽,例如,标簽option對應的父标簽是select,包裹深度為1。html文法要求這些标簽必須包含在其對應的父标簽中,在随後設定臨時div元素的innerhtml屬性之前,會在便簽前後自動加上父标簽和關閉标簽。
關于對象wrapmap,有幾個有趣的地方需要注意一下:
标簽option需要包含在多選的<select multiple='multiple'>中。因為如果包含在單選的<selector>中,建立的第一個option元素的selected屬性會被浏覽器預設設定為true;而如果包含在多選的<select
multiple='multiple'>中,則不會被浏覽器修改。可以用下面的代碼驗證:
// 建立一個臨時 div
var div = document.createelement('div');
// 單選 <selector>,輸出 true
div.innerhtml =
'<select><option>0</option></select>';
console.log( div.getelementsbytagname(
'option' )[0].selected ); // true
// 多選 <select
multiple="multiple">,輸出 false
div.innerhtml = '<select
multiple="multiple"><option>0</option></select>';
'option' )[0].selected ); // false
嚴格按照html文法為tr、td、th添加父标簽tbody。雖然設定innerhtml後浏覽器會為tr、td、th自動添加父元素tbody,但保持結構良好的html代碼是個好習慣。
在ie 9以下的浏覽器中,不能序列化标簽<link>和<script>,即通過浏覽器的innerhtml機制不能将其轉換為對應的link元素和script元素,此時測試項jquery.support.htmlserialize為false。解決方案是在标簽<link>和<script>外包裹一層元素再轉換。包裹的元素定義在wrapmap._default中,_default預設為[0, "",""],如果jquery.support.htmlserialize為false,則會在第5675行被修正為[1, "div<div>",
"</div>"]。可以運作用下面的代碼來驗證,運作結果如圖2-6所示。
// ie 9 以下的浏覽器
// link 不包裹元素,輸出 0
div.innerhtml = '<link
rel="stylesheet" type="text/css"
href="test.css" />';
console.log(
div.getelementsbytagname("link").length ); // 0
// link 包裹元素,輸出 1
div.innerhtml = 'div<div><link
href="test.css" />
</div>';
div.getelementsbytagname("link").length ); // 1
// script不包裹元素,輸出 0
'<script></script>';
div.getelementsbytagname("script").length ); // 0
// script 包裹元素,輸出 1
'div<div><script></script></div>';
div.getelementsbytagname("script").length ); // 1
圖2-6 序列化标簽<link>和<script>
對對象wrapmap和測試jquery.support.htmlserialize的分析到此為止,關于對象wrapmap中标簽的含義和文法請參考相關的基礎書籍,更多關于浏覽器的測試和修正請參考第7章。
下面回到對方法jquery.clean()源碼的分析。
第6288行:取出被包裹的深度指派給變量depth,稍後将依據該變量層層剝去包裹的父元素。
第6289行:建立一個臨時div元素,稍後它會被添加到一個安全文檔片段中,并且它的innerhtml屬性會被設定為待轉換的html代碼。
(4)把臨時div元素插入一個安全文檔片段中
6291 //
append wrapper element to unknown element safe doc fragment
6292 if ( context === document
) {
6293 //
use the fragment we've already created for this document
6294
safefragment.appendchild( div );
6295 } else {
6296 //
use a fragment created with the owner document
6297 createsafefragment(
context ).appendchild( div );
6298 }
6299
第6292~6298行:如果傳入的文檔對象context是目前文檔對象,則把臨時div元素插入已建立的安全文檔片段safefragment中;否則,調用函數createsafefragment()在文檔對象context上建立一個新的安全文檔片段,然後插入臨時div元素。
所謂“安全”,是指不支援html5的浏覽器也能夠正确地解析和渲染未知的html5标簽,即能夠正确地建構dom樹,并且可以為之設定樣式。ie 9以下的浏覽器不支援html5,如果遇到未知标簽(如<article>),浏覽器會向dom樹中插入一個沒有子元素的空元素。針對這個問題有一個“莫名其妙”的解決方法,就是在使用未知标簽之前調用document.createelement( '未知标簽' )建立一個對應的dom元素,這樣就可以“教會”浏覽器正确地解析和渲染這個未知标簽。
安全文檔片段safefragment在jquery初始化時由函數createsafefragment()建立,相關代碼如下所示:
5628 function
createsafefragment( document ) {
5629
var list = nodenames.split( "|" ),
5630
safefrag = document.createdocumentfragment();
5631
5632
if ( safefrag.createelement ) {
5633
while ( list.length ) {
5634 safefrag.createelement(
5635 list.pop()
5636 );
5637
5638
5639
return safefrag;
5640 }
5642 var nodenames =
"abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|"
+
5643
"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
變量nodenames中存放了所有的html5标簽,函數createsafefragment()在傳入的文檔對象document上建立一個新的文檔片段,然後在該文檔片段上逐個建立html5元素,進而“教會”不支援html5的浏覽器正确地解析和渲染html5标簽。
(5)利用浏覽器的innerhtml機制将html代碼轉換為dom元素
先為html代碼包裹必要的父标簽,然後指派給臨時div元素的innerhtml屬性,進而将html代碼轉換為dom元素,之後再層層剝去包裹的父元素,得到轉換後的dom元素。相關代碼如下所示:
6300 // go to html and back,
then peel off extra wrappers
6301 div.innerhtml = wrap[1] +
elem + wrap[2];
6302
6303 // move to the right depth
6304 while ( depth-- ) {
6305 div = div.lastchild;
6306 }
6307
第6300行:為html代碼包裹必要的父标簽,然後指派給臨時div元素的innerhtml屬性,浏覽器會自動生成dom元素。
第6304~6306行:用while循環層層剝去包裹的父元素,最終變量div将指向html代碼對應的dom元素的父元素。例如,标簽<option>會被标簽<select>包裹,包裹深度為1,剝去一層後變量div指向了select元素。再例如,标簽<td>會被<table><tbody><tr>包裹,包裹深度為3,剝去3層後變量div指向了tr元素。如果html代碼不需要包裹父标簽,則變量depth為0,不會進入while循環。
(6)移除ie 6/7自動插入的空tbody元素
6308 // remove ie's
autoinserted <tbody> from table fragments
6309 if ( !jquery.support.tbody
6310
6311 // string was a
<table>, *may* have spurious <tbody>
6312 var hasbody =
rtbody.test(elem),
6313 tbody = tag ===
"table" && !hasbody ?
6314 div.firstchild
&& div.firstchild.childnodes :
6315
6316 // string was
a bare <thead> or <tfoot>
6317 wrap[1] ===
"<table>" && !hasbody ?
6318
div.childnodes :
6319 [];
6320
6321 for ( j = tbody.length
- 1; j >= 0 ; --j ) {
6322 if (
jquery.nodename( tbody[ j ], "tbody" ) && !
tbody[ j ].childnodes.length ) {
6323 tbody[ j
].parentnode.removechild( tbody[ j ] );
6324 }
6325 }
6326 }
6327
第6309行:在ie 6/7中,浏覽器會為空table元素自動插入空tbody元素,此時測試項jquery.support.tbody為false。空元素指沒有子元素的元素。
第6312行:用正則rtbody檢查html代碼中是否含有tbody标簽,該正則的定義代碼如下:
5648
rtbody = /<tbody/i,
第6313~6319行:提取ie 6/7自動插入的空tbody元素。這個複合三元表達式有些複雜,下面逐段分解之。
1)如果執行“tag ===
"table"&&!hasbody ? div.firstchild &&
div.firstchild.childnodes:”表示html代碼中含有table标簽,沒有tbody标簽,浏覽器生成dom元素時可能自動插入空tbody元素。此時變量div指向div元素,div.firstchild指向table元素,div.firstchild.childnodes則是tbody、thead、tfoot、colgroup、caption的元素集合。
2)如果執行“wrap[1]
==="<table>"&& !hasbody ? div.childnodes:”表示為html代碼包裹了父标簽<table>,但是html代碼中沒有tbody标簽,即html代碼中含有thead、tfoot、colgroup、caption之一或多個,浏覽器生成dom元素時可能自動插入空tbody元素。此時變量div指向table元素,div.childnodes是tbody、thead、tfoot、colgroup、caption的元素集合。
3)[]。如果html代碼中含有tbody标簽,無論空或非空都不需要删除,是以是[]。在其他情況下,浏覽器生成dom元素時不會自動插入空tbody元素,仍然是[]。
第6321~6326行:周遊數組tbody,移除空tbody元素,非空tbody元素不會移除。
第6322行:因為數組tbody中的元素還可能是thead、tfoot、colgroup、caption元素,是以要先判斷tbody[j]是否是tbody元素;因為前面“提取ie 6/7自動插入的空tbody元素”的代碼已經覆寫了所有可能的情況,是以!tbody[j].childnodes.length屬于防禦性檢查,以防萬一。
(7)插入ie 6/7/8自動剔除的前導空白符
6328 // ie completely kills
leading whitespace when innerhtml is used
6329 if (
!jquery.support.leadingwhitespace && rleading whitespace.test( elem ) )
{
6330 div.insertbefore(
context.createtextnode( rleading
whitespace.exec(elem)[0] ), div.firstchild
);
6331 }
6332
第6329行:在ie 6/7/8中,設定innerhtml屬性時,浏覽器會自動剔除前導空白符,測試測試項jquery.support.leadingwhitespace為false。用正則rleadingwhitespace檢測html代碼中是否含有前導空白符,該正則的定義代碼如下所示:
5645
rleadingwhitespace = /^\s+/,
第6330行:用正則rleadingwhitespace提取html代碼中的前導空白符,然後調用原生方法createtextnode()建立文本節點,最後插入div元素的第一個子元素前。
(8)取到轉換後的dom元素集合
6333 elem = div.childnodes;
6334 }
6335 }
6336
(9)在ie 6/7中修正複選框和單選按鈕的選中狀态
6337 // resets defaultchecked for any
radios and checkboxes
6338 // about to be appended to the dom
in ie 6/7 (#8060)
6339 var len;
6340 if ( !jquery.support.appendchecked
6341
if ( elem[0] &&
typeof (len = elem.length) === "number" ) {
6342 for ( j = 0; j < len;
j++ ) {
6343 findinputs( elem[j] );
6344 }
6345 } else {
6346 findinputs( elem );
6347 }
6348 }
6349
第6340行:在ie 6/7 中,複選框和單選按鈕插入dom樹後,其選中狀态checked會丢失,此時測試項jquery.support.appendchecked為false。通過在插入之前把屬性
checked的值指派給屬性defaultchecked,可以解決這個問題。
第6341~6344行:周遊轉換後的dom元素集合,在每個元素上調用函數findinputs( elem )。函數findinputs( elem )會找出其中的複選框和單選按鈕,并調用函數fixdefaultchecked( elem )把屬性checked的值指派給屬性defaultchecked。
函數findinputs()和fixdefaultchecked()的相關代碼如下所示:
6175 // used in clean,
fixes the defaultchecked property
6176 function
fixdefaultchecked( elem ) {
6177
if ( elem.type === "checkbox" || elem.type ===
"radio" ) {
6178
elem.defaultchecked = elem.checked;
6179
6180 }
6181 // finds all inputs
and passes them to fixdefaultchecked
6182 function findinputs(
elem ) {
6183
var nodename = ( elem.nodename || "" ).tolowercase();
6184
if ( nodename === "input" ) {
6185
fixdefaultchecked( elem );
6186
// skip scripts, get other children
6187
} else if ( nodename !== "script" && typeof
elem.getelementsbytagname !== "undefined" ) {
6188
jquery.grep( elem.getelementsbytagname("input"),
fixdefaultchecked );
6189
6190 }
6191
(10)合并轉換後的dom元素
6350 if ( elem.nodetype ) {
6351 ret.push( elem );
6352 } else {
6353 ret = jquery.merge( ret, elem
6354 }
6355
6356
第6355行:到此,數組elems中的所有html代碼都已轉換為dom元素,并合并到了數組ret中。
4.?傳入文檔片段fragment的情況
如果傳入了文檔片段fragment,則周遊數組ret,提取所有(包括子元素)合法的script元素存入數組scripts,并把其他元素插入文檔片段fragment。相關代碼如下所示:
6357
if ( fragment ) {
6358 checkscripttype = function( elem )
6359 return !elem.type ||
rscripttype.test( elem.type );
6360
};
6361 for ( i = 0; ret[i]; i++ ) {
6362 if ( scripts &&
jquery.nodename( ret[i], "script" ) && (!ret[i].type ||
ret[i].type.tolowercase() === "text/javascript") ) {
6363 scripts.push( ret[i].parentnode
? ret[i].parentnode.remove child( ret[i] ) : ret[i] );
6364
6365 } else {
6366 if ( ret[i].nodetype === 1
6367 var jstags =
jquery.grep( ret[i].getelementsbytagname ( "script" ), checkscripttype
6368
6369 ret.splice.apply( ret,
[i + 1, 0].concat( jstags ) );
6370 }
6371 fragment.appendchild(
ret[i] );
6372 }
6373 }
6374
6375
第6358~6360行:初始化變量checkscripttype為一個函數,用于檢測script元素是否是可執行。如果一個script元素沒有指定屬性type,或者屬性type的值含有“/javascript”或“/ecmascript”,則認為是可執行的。正則rscripttype的定義代碼如下:
5655
rscripttype = /\/(java|ecma)script/i,
第6361~6374行:周遊數組ret,提取所有(包括子元素)合法的script元素存入數組scripts,其他元素則插入文檔片段fragment。
第6362~6363行:如果調用方法jquery.clean()時傳入了數組scripts,并找到了合法的script元素,則将該元素從其父元素中移除,然後存入數組scripts。如果一個script元素沒有指定屬性type,或者屬性type的值是“text/javascript”,則認為是合法的。
通常應該為方法jquery.clean()同時傳入文檔片段fragment和數組scripts。
第6366~6370行:在周遊數組ret的過程中,會提取目前元素所包含的script元素,并把其中可執行的插入數組ret,插入位置在目前元素之後,以便繼續執行第6362~6363行的檢測和提取。script元素是否可執行通過函數checkscripttype( elem )檢測。
第6371行:在周遊數組ret的過程中,會把除了合法script元素之外的所有元素插入文檔片段。
5.?傳回轉換後的dom元素數組
6376
return ret;
6377
第6376行:最後,傳回數組ret,其中包含了所有轉換後的dom元素。但是要注意,如果傳入了文檔片段fragment和數組scripts,那麼調用jquery.clean()的代碼應該從文檔片段fragment中讀取轉換後的dom元素,并從數組scripts中讀取合法的script元素;如果未傳入,則隻能使用傳回值ret。
<b>2.5.3 小結</b>
方法jquery.clean( elems, context, fragment, scripts )的執行過程可以總結為圖2-7。
圖2-7 jquery.clean( elems, context, fragment, scripts )的執行過程
2.2~2.5節詳細分析了建構
jquery 對象的源碼實作,從下一節開始,将介紹和分析其他的原型屬性和方法,以及靜态屬性和方法,這些屬性和方法是jquery庫的基礎。