天天看點

jQuery技術内幕:深入解析jQuery架構設計與實作原理. 2.5 jQuery.clean( elems, context, fragment, scripts )

<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代碼中含有需要包裹在父标簽中的子标簽,例如,子标簽&lt;option&gt;需要包裹在父标簽&lt;select&gt;中,方法jquery.clean()會先在html代碼的前後加上父标簽和關閉标簽,在設定臨時div元素的innerhtml屬性生成dom元素後,再層層剝去包裹的父元素,取出html代碼對應的dom元素。

如果html代碼中含有&lt;script&gt;标簽,為了能執行 &lt;script&gt;标簽所包含的javascript代碼或引用的javascript檔案,在設定臨時div元素的innerhtml屬性生成dom元素後,方法jquery.clean()會提取其中的script元素放入數組scripts。注意,将含有&lt;script&gt;标簽的html代碼設定給某個元素的innerhtml屬性後,&lt;script&gt;标簽所包含的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] &amp;&amp; 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 = /&lt;|&amp;#?\w+;/,

标簽的特征字元是左尖括号“&lt;”,字元代碼的特征字元是“&amp;”,數字代碼的特征字元是“&amp;#”。字元代碼和數字代碼是特殊符号的兩種形式,常見的特殊符号與字元代碼、數字代碼的編碼對照表如表2-3所示。

表2-3 常見特殊符号與字元代碼、數字代碼的編碼對照表

殊 符 号  字

符 代 碼  數

字 代 碼  備  注

"       &amp;quot;     &amp;#34;       雙引号

'        &amp;apos;     &amp;#39;       單引号

&lt;       &amp;lt;  &amp;#60;       小于

&gt;       &amp;#62;       &amp;gt; 大于

&amp;      &amp;amp;      &amp;#38;      

         &amp;nbsp;     &amp;#160;     空格

?       &amp;copy;     &amp;#169;     版權符号

?       &amp;reg;        &amp;#174;     注冊符号

?       &amp;trade;    &amp;#8482;  商标符号

完整的特殊符号編碼對照表請通路:

http://www.w3.org/markup/guide/advanced.html

http://www.w3schools.com/tags/ref_entities.asp

第6280行:原生方法document.createtextnode()用于建立文本節點,但是對于傳給它的字元串參數不會做轉義解析,也就是說,該方法不能正确地解析和建立包含了字元代碼或數字代碼的字元串,而浏覽器的innerhtml機制則可以。例如,下面的代碼将在頁面中輸出“?&amp;copy”:

document.body.innerhtml = '&amp;copy;';

// 顯示轉義後的"@"

document.body.appendchild(

document.createtextnode('&amp;copy;') );

// 顯示原始字元串"&amp;copy;"

第6281行:從這行代碼開始轉換包含了标簽、字元代碼或數字代碼的html代碼。

(2)修正自關閉标簽

6282                     // fix

"xhtml"-style tags in all browsers

6283                     elem =

elem.replace(rxhtmltag, "&lt;$1&gt;&lt;/$2&gt;");

6284

第6282~6283行:用正則rxhtmltag比對html代碼中的自關閉标簽,并通過方法replace()替換為成對的标簽,例如,&lt;div/&gt;會被修正為&lt;div&gt;&lt;/div&gt;。自關閉标簽是指沒有對應的關閉标簽,而是在标簽的最後加一個"/"來關閉它,例如,&lt;div/&gt;。方法replace()的第二個參數中的$1和$2分别對應正則rxhtmltag的第一個和第二個分組。

正則rxhtmltag是修正自關閉标簽的關鍵所在,它的定義代碼如下:

5646    

rxhtmltag =

/&lt;(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^&gt;]*)\/&gt;/ig,

這個正則有些長,可以先用幾個例子來測試一下它的功能。

例1 最簡單的自關閉标簽。

'&lt;div/&gt;'.replace( rxhtmltag,

'&lt;$1&gt;&lt;/$2&gt;' );

// 輸出:&lt;div&gt;&lt;/div&gt;

例2 帶有屬性的自關閉标簽。

'&lt;div

class="abc"/&gt;'.replace( rxhtmltag, '&lt;$1&gt;&lt;/$2&gt;' );

// 輸出:&lt;div

class="abc"&gt;&lt;/div&gt;

例3 多個标簽組合、标簽前後有其他字元、大寫标簽&lt;a&gt;&lt;/a&gt;、不需要關閉的标簽&lt;input&gt;、多行标簽\n。

'a&lt;div/&gt;a \t b&lt;a/&gt;b \n c&lt;xyz/&gt;c

\n d&lt;img/&gt;d \r\n e&lt;input&gt;e'.replace( rxhtmltag,

// 輸出:"a&lt;div&gt;&lt;/div&gt;a

\t b&lt;a&gt;&lt;/a&gt;b \n c&lt;xyz&gt;&lt;/xyz&gt;c \n d&lt;img/&gt;d \r\n

e&lt;input&gt;e"

現在來看看正則rxhtmltag的精髓之處。

首先過濾掉不需要修正的标簽:(?!area|br|col|embed|hr|img|input|link|meta|param)。(?!p)是反前向聲明,要求接下來的字元不與模式p比對。如果html代碼中出現了這些标簽,則不做任何處理。例如:

'&lt;areadiv/&gt;'.replace( rxhtmltag,

'&lt;$1&gt;&lt;/$2&gt;' )

// 輸出:&lt;areadiv/&gt;

然後是精妙的嵌套分組,巧妙地提取了标簽,同時又保留了屬性:&lt;和/&gt;包圍的(([\w:]+)[^&gt;]*)作為第一個分組,其中包含了标簽和屬性;嵌套的([\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 = /&lt;([\w:]+)/,

第6287行:從對象wrapmap中取出标簽tag對應的父标簽,它的定義和初始化代碼如下:

5657    

wrapmap = {

5658       

option: [ 1, "&lt;select multiple='multiple'&gt;",

"&lt;/select&gt;" ],

5659       

legend: [ 1, "&lt;fieldset&gt;", "&lt;/fieldset&gt;"

],

5660       

thead: [ 1, "&lt;table&gt;", "&lt;/table&gt;" ],

5661       

tr: [ 2, "&lt;table&gt;&lt;tbody&gt;", "&lt;/tbody&gt;&lt;/table&gt;"

5662       

td: [ 3, "&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;",

"&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;" ],

5663       

col: [ 2,

"&lt;table&gt;&lt;tbody&gt;&lt;/tbody&gt;&lt;colgroup&gt;",

"&lt;/colgroup&gt;&lt;/table&gt;" ],

5664       

area: [ 1, "&lt;map&gt;", "&lt;/map&gt;" ],

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

&lt;link&gt; and &lt;script&gt; tags normally

5674 if (

!jquery.support.htmlserialize ) {

5675    

wrapmap._default = [ 1, "div&lt;div&gt;",

"&lt;/div&gt;" ];

5676 }

在對象wrapmap中包含了以下标簽:option、optgroup、legend、thead、tbody、tfoot、colgroup、caption、tr、td、th、col、area、_default,每個标簽對應一個數組,數組中的元素依次是:包裹的深度、包裹的父标簽、父标簽對應的關閉标簽,例如,标簽option對應的父标簽是select,包裹深度為1。html文法要求這些标簽必須包含在其對應的父标簽中,在随後設定臨時div元素的innerhtml屬性之前,會在便簽前後自動加上父标簽和關閉标簽。

關于對象wrapmap,有幾個有趣的地方需要注意一下:

标簽option需要包含在多選的&lt;select multiple='multiple'&gt;中。因為如果包含在單選的&lt;selector&gt;中,建立的第一個option元素的selected屬性會被浏覽器預設設定為true;而如果包含在多選的&lt;select

multiple='multiple'&gt;中,則不會被浏覽器修改。可以用下面的代碼驗證:

// 建立一個臨時 div

var div = document.createelement('div');

// 單選 &lt;selector&gt;,輸出 true

div.innerhtml =

'&lt;select&gt;&lt;option&gt;0&lt;/option&gt;&lt;/select&gt;';

console.log( div.getelementsbytagname(

'option' )[0].selected ); // true

// 多選 &lt;select

multiple="multiple"&gt;,輸出 false

div.innerhtml = '&lt;select

multiple="multiple"&gt;&lt;option&gt;0&lt;/option&gt;&lt;/select&gt;';

'option' )[0].selected ); // false

嚴格按照html文法為tr、td、th添加父标簽tbody。雖然設定innerhtml後浏覽器會為tr、td、th自動添加父元素tbody,但保持結構良好的html代碼是個好習慣。

在ie 9以下的浏覽器中,不能序列化标簽&lt;link&gt;和&lt;script&gt;,即通過浏覽器的innerhtml機制不能将其轉換為對應的link元素和script元素,此時測試項jquery.support.htmlserialize為false。解決方案是在标簽&lt;link&gt;和&lt;script&gt;外包裹一層元素再轉換。包裹的元素定義在wrapmap._default中,_default預設為[0, "",""],如果jquery.support.htmlserialize為false,則會在第5675行被修正為[1, "div&lt;div&gt;",

"&lt;/div&gt;"]。可以運作用下面的代碼來驗證,運作結果如圖2-6所示。

// ie 9 以下的浏覽器

// link 不包裹元素,輸出 0

div.innerhtml = '&lt;link

rel="stylesheet" type="text/css"

href="test.css"   /&gt;';

console.log(

div.getelementsbytagname("link").length );    // 0

// link 包裹元素,輸出 1

div.innerhtml = 'div&lt;div&gt;&lt;link

href="test.css"   /&gt;

&lt;/div&gt;';

div.getelementsbytagname("link").length );     // 1

// script不包裹元素,輸出 0

'&lt;script&gt;&lt;/script&gt;';

div.getelementsbytagname("script").length );         // 0

// script 包裹元素,輸出 1

'div&lt;div&gt;&lt;script&gt;&lt;/script&gt;&lt;/div&gt;';

div.getelementsbytagname("script").length ); // 1

圖2-6 序列化标簽&lt;link&gt;和&lt;script&gt;

對對象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,如果遇到未知标簽(如&lt;article&gt;),浏覽器會向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元素的父元素。例如,标簽&lt;option&gt;會被标簽&lt;select&gt;包裹,包裹深度為1,剝去一層後變量div指向了select元素。再例如,标簽&lt;td&gt;會被&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;包裹,包裹深度為3,剝去3層後變量div指向了tr元素。如果html代碼不需要包裹父标簽,則變量depth為0,不會進入while循環。

(6)移除ie 6/7自動插入的空tbody元素

6308                     // remove ie's

autoinserted &lt;tbody&gt; from table fragments

6309                     if ( !jquery.support.tbody

6310

6311                         // string was a

&lt;table&gt;, *may* have spurious &lt;tbody&gt;

6312                         var hasbody =

rtbody.test(elem),

6313                             tbody = tag ===

"table" &amp;&amp; !hasbody ?

6314                                 div.firstchild

&amp;&amp; div.firstchild.childnodes :

6315

6316                                 // string was

a bare &lt;thead&gt; or &lt;tfoot&gt;

6317                                 wrap[1] ===

"&lt;table&gt;" &amp;&amp; !hasbody ?

6318                                    

div.childnodes :

6319                                     [];

6320

6321                         for ( j = tbody.length

- 1; j &gt;= 0 ; --j ) {

6322                             if (

jquery.nodename( tbody[ j ], "tbody" ) &amp;&amp; !

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 = /&lt;tbody/i,

第6313~6319行:提取ie 6/7自動插入的空tbody元素。這個複合三元表達式有些複雜,下面逐段分解之。

1)如果執行“tag ===

"table"&amp;&amp;!hasbody ? div.firstchild &amp;&amp;

div.firstchild.childnodes:”表示html代碼中含有table标簽,沒有tbody标簽,浏覽器生成dom元素時可能自動插入空tbody元素。此時變量div指向div元素,div.firstchild指向table元素,div.firstchild.childnodes則是tbody、thead、tfoot、colgroup、caption的元素集合。

2)如果執行“wrap[1]

==="&lt;table&gt;"&amp;&amp; !hasbody ? div.childnodes:”表示為html代碼包裹了父标簽&lt;table&gt;,但是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 &amp;&amp; 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] &amp;&amp;

typeof (len = elem.length) === "number" ) {

6342                     for ( j = 0; j &lt; 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" &amp;&amp; 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 &amp;&amp;

jquery.nodename( ret[i], "script" ) &amp;&amp; (!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庫的基礎。

繼續閱讀