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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

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库的基础。

相关文章
|
5天前
|
运维 Kubernetes Cloud Native
云原生技术:容器化与微服务架构的完美结合
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其灵活性和高效性成为企业的新宠。本文将深入探讨云原生的核心概念,包括容器化技术和微服务架构,以及它们如何共同推动现代应用的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务,揭示云原生技术的强大能力和未来潜力。
|
13天前
|
运维 持续交付 API
从零构建微服务架构:一次深度技术探索之旅####
【10月更文挑战第28天】 本文记录了作者在从零开始构建微服务架构过程中的深刻技术感悟,通过实战案例详细剖析了微服务设计、开发、部署及运维中的关键要点与挑战。文章首先概述了微服务架构的核心理念及其对企业IT架构转型的重要性,随后深入探讨了服务拆分策略、API网关选型、服务间通信协议选择、容器化部署(Docker+Kubernetes)、以及持续集成/持续部署(CI/CD)流程的设计与优化。最后,分享了在高并发场景下的性能调优经验与故障排查心得,旨在为读者提供一套可借鉴的微服务架构实施路径。 ####
52 3
|
3天前
|
存储 分布式计算 关系型数据库
架构/技术框架调研
本文介绍了微服务间事务处理、调用、大数据处理、分库分表、大文本存储及数据缓存的最优解决方案。重点讨论了Seata、Dubbo、Hadoop生态系统、MyCat、ShardingSphere、对象存储服务和Redis等技术,提供了详细的原理、应用场景和优缺点分析。
|
3天前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
5天前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
24 7
|
3天前
|
传感器 算法 物联网
智能停车解决方案之停车场室内导航系统(二):核心技术与系统架构构建
随着城市化进程的加速,停车难问题日益凸显。本文深入剖析智能停车系统的关键技术,包括停车场电子地图编辑绘制、物联网与传感器技术、大数据与云计算的应用、定位技术及车辆导航路径规划,为读者提供全面的技术解决方案。系统架构分为应用层、业务层、数据层和运行环境,涵盖停车场室内导航、车位占用检测、动态更新、精准导航和路径规划等方面。
27 4
|
4天前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用架构中的实践与思考
【10月更文挑战第38天】随着云计算的不断成熟和演进,云原生(Cloud-Native)已成为推动企业数字化转型的重要力量。本文从云原生的基本概念出发,深入探讨了其在现代应用架构中的实际应用,并结合代码示例,展示了云原生技术如何优化资源管理、提升系统弹性和加速开发流程。通过分析云原生的优势与面临的挑战,本文旨在为读者提供一份云原生转型的指南和启示。
18 3
|
6天前
|
网络协议 数据挖掘 5G
适用于金融和交易应用的低延迟网络:技术、架构与应用
适用于金融和交易应用的低延迟网络:技术、架构与应用
31 5
|
4天前
|
运维 Kubernetes Cloud Native
云原生技术在现代应用架构中的实践与挑战####
本文深入探讨了云原生技术的核心概念、关键技术组件及其在实际项目中的应用案例,分析了企业在向云原生转型过程中面临的主要挑战及应对策略。不同于传统摘要的概述性质,本摘要强调通过具体实例揭示云原生技术如何促进应用的灵活性、可扩展性和高效运维,同时指出实践中需注意的技术债务、安全合规等问题,为读者提供一幅云原生技术实践的全景视图。 ####
|
8天前
|
Kubernetes Cloud Native 云计算
云原生技术深度解析:重塑企业IT架构的未来####
本文深入探讨了云原生技术的核心理念、关键技术组件及其对企业IT架构转型的深远影响。通过剖析Kubernetes、微服务、容器化等核心技术,本文揭示了云原生如何提升应用的灵活性、可扩展性和可维护性,助力企业在数字化转型中保持领先地位。 ####

推荐镜像

更多