jQuery技术内幕:深入解析jQuery架构设计与实现原理. 2.5 jQuery.clean( elems, context, fragment, scripts )

简介:

2.5 jQuery.clean( elems, context, fragment, scripts )

2.5.1 实现原理

方法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, '<$1></$2>' );

// 输出:"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>';

console.log( div.getElementsByTagName( '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 以下的浏览器

// 创建一个临时 div

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

// 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 rel="stylesheet" type="text/css" href="test.css"   /> </div>';

console.log( div.getElementsByTagName("link").length );     // 1

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

div.innerHTML = '<script></script>';

console.log( div.getElementsByTagName("script").length );         // 0

// script 包裹元素,输出 1

div.innerHTML = 'div<div><script></script></div>';

console.log( 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()创建,相关代码如下所示:

5667     safeFragment = createSafeFragment( document );

 

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。

2.5.3 小结

方法jQuery.clean( elems, context, fragment, scripts )的执行过程可以总结为图2-7。

 

图2-7 jQuery.clean( elems, context, fragment, scripts )的执行过程

2.2~2.5节详细分析了构建 jQuery 对象的源码实现,从下一节开始,将介绍和分析其他的原型属性和方法,以及静态属性和方法,这些属性和方法是jQuery库的基础。

相关文章
|
28天前
|
机器学习/深度学习 前端开发 Windows
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
32 0
|
28天前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
32 0
|
15天前
|
存储 中间件 关系型数据库
数据库切片大对决:ShardingSphere与Mycat技术解析
数据库切片大对决:ShardingSphere与Mycat技术解析
24 0
|
28天前
|
存储 NoSQL 算法
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)(二)
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)
45 0
|
1天前
|
大数据 图形学 云计算
EDA设计:技术深度解析与实战代码应用
EDA设计:技术深度解析与实战代码应用
|
4天前
|
JavaScript 前端开发 UED
深入解析JavaScript原生操作DOM技术
【4月更文挑战第22天】本文深入探讨JavaScript原生DOM操作技术,包括使用`getElement*`方法和CSS选择器获取元素,借助`createElement`与`appendChild`动态创建及插入元素,修改元素内容、属性和样式,以及删除元素。通过掌握这些技术,开发者能实现页面动态交互,但应注意避免过度操作DOM以优化性能和用户体验。
|
5天前
|
存储 安全 网络安全
解析企业邮箱迁移:从技术到策略的完全指南
公司邮箱迁移是业务连续性和数据安全的关键步骤。涉及数据加密、安全存储和密钥管理,确保转移过程中的完整性与机密性。迁移应尽量减少对业务影响,通过IMAP/POP协议实现无缝转移。以Zoho Mail为例,需开启服务,获取授权码,设置转移,选择内容,填写原邮箱信息,最后验证数据。迁移前后注意备份和问题解决,确保顺利进行。
9 0
|
14天前
|
存储 人工智能 编译器
存算一体新兴力量:解析我国企业在存储创新、技术路径上的多元化探索
存算一体新兴力量:解析我国企业在存储创新、技术路径上的多元化探索
|
28天前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
51 0
|
28天前
|
canal 消息中间件 关系型数据库
【分布式技术专题】「分布式技术架构」MySQL数据同步到Elasticsearch之N种方案解析,实现高效数据同步
【分布式技术专题】「分布式技术架构」MySQL数据同步到Elasticsearch之N种方案解析,实现高效数据同步
78 0

推荐镜像

更多