jQuery.clean源码分析

一、jQuery.clean使用方法

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

二、思路分析

1、处理参数context,确保其为文档根节点document

2、处理参数elems数组(循环遍历数组)

  2.1、elem为数字,转换为字符串

  2.2、elem为非法值,跳出本次循环

  2.3、elem为字符串

  2.4、字符串不存在实体编号或html标签,则创建文本节点

  2.5、字符串为实体编号或html标签

1 创建一个div元素并插入到文档碎片中
2 处理xhtml风格标签
3 将elem包裹起来,并将包裹后的字符串作为div的innerHTML
4 如果包裹深度大于1,只留下第一层包裹元素
5 清除在ie6,7中空table标签自动加入的tbody
6 将在ie9以下浏览器中剔除的开头空白字符串作为div元素的第一个文本子节点
7 将elem重新赋值为div的子节点集合(nodeList对象),
8 移除本次循环中文档碎片中的div,保持下一次循环中干净的div元素    

  2.3、如果elem为文本节点,则直接添加到要返回的ret数组中,否则将elem(nodeList对象)中的节点合并到数组

  2.4、修复在ie6、7中type为radio,checkbox类型的节点的选中状态(checked)失效的bug

3、处理参数fragment

  3.1、将ret中各节点添加到文档碎片fragment中

  3.2、提取节点中的script子节点,并将其添加到ret数组中,添加的script位置为其原父元素位置后面

4、返回ret数组

三、源码注释分析

1、函数中用到的变量及函数

 1 var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
 2         "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
 3         
 4     wrapMap = {
 5         option: [ 1, "<select multiple='multiple'>", "</select>" ],
 6         legend: [ 1, "<fieldset>", "</fieldset>" ],
 7         thead: [ 1, "<table>", "</table>" ],
 8         tr: [ 2, "<table><tbody>", "</tbody></table>" ],
 9         td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
10         col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
11         area: [ 1, "<map>", "</map>" ],
12         _default: [ 0, "", "" ]
13     },
14 
15     rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
16     rtagName = /<([\w:]+)/,
17     rtbody = /<tbody/i,
18     rhtml = /<|&#?\w+;/,
19     rleadingWhitespace = /^\s+/,
20     rcheckableType = /^(?:checkbox|radio)$/,
21     rscriptType = /\/(java|ecma)script/i;
22 
23 
24 // 设置复选框checkbox或单选框radio表单元素的默认选中状态
25 function fixDefaultChecked( elem ) {
26     if ( rcheckableType.test( elem.type ) ) {
27         elem.defaultChecked = elem.checked;
28     }
29 }
30 
31 // 创建一个安全的文档碎片
32 function createSafeFragment( document ) {
33     var list = nodeNames.split( "|" ),
34     safeFrag = document.createDocumentFragment(); // ie6,7,8浏览器把safeFrage作为HTMLDocument类型
35 
36     // 针对ie9以下浏览器
37     if ( safeFrag.createElement ) {
38         while ( list.length ) {
39             safeFrag.createElement(
40                 list.pop()
41             );
42         }
43     }
44     return safeFrag;
45 }
46 
47 // 模拟ES5中Array的新功能
48 // 该函数API:http://www.css88.com/jqapi-1.8/#p=jQuery.grep
49 jQuery.extend({
50     grep: function( elems, callback, inv ) {
51         var retVal,
52             ret = [],
53             i = 0,
54             length = elems.length;
55         inv = !!inv;
56 
57         // Go through the array, only saving the items
58         // that pass the validator function
59         for ( ; i < length; i++ ) {
60             retVal = !!callback( elems[ i ], i );
61             if ( inv !== retVal ) {
62                 ret.push( elems[ i ] );
63             }
64         }
65         return ret;
66     }              
67 });

2、源码分析

【基于jQuery1.8.3】

  1 jQuery.extend({
  2     clean: function( elems, context, fragment, scripts ) {
  3 
  4         // 声明变量
  5         var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
  6             safe = context === document && safeFragment,
  7             ret = [];
  8         
  9         // 确保变量context为文档根节点document
 10         if ( !context || typeof context.createDocumentFragment === "undefined" ) {
 11             context = document;
 12         }
 13 
 14         // Use the already-created safe fragment if context permits
 15         for ( i = 0; (elem = elems[i]) != null; i++ ) {
 16 
 17             // 如果elem为数字,则将其转换为字符串
 18             if ( typeof elem === "number" ) {
 19                 elem += "";
 20             }
 21 
 22             // 如果elem为undefined,跳出本次循环
 23             if ( !elem ) {
 24                 continue;
 25             }
 26 
 27             // Convert html string into DOM nodes
 28             // 转换数组项(字符串)为DOM节点
 29             if ( typeof elem === "string" ) {
 30                 
 31                 // 如果不存在html实体编号或标签,则创建文本节点
 32                 if ( !rhtml.test( elem ) ) {
 33                     elem = context.createTextNode( elem );
 34                 }
 35                 // 处理是html标签字符串的数组项
 36                 else {
 37                     // Ensure a safe container in which to render the html
 38                     // safe为#document-fragment类型,在ie9以下浏览器中,safe为HTMLDocument类型节点,且nodeNames数组为空
 39                     safe = safe || createSafeFragment( context );
 40                     
 41                     // 创建一个div元素并将其插入到文档碎片中
 42                     div = context.createElement("div");
 43                     safe.appendChild( div );
 44 
 45                     // Fix "XHTML"-style tags in all browsers
 46                     // 除了area,br,col,embed,hr,img,input,link,meta,param这些标签外,
 47                     // 将开始标签末尾加入斜杠的标签转换为开始和结束标签
 48                     elem = elem.replace(rxhtmlTag, "<$1></$2>");
 49 
 50                     // Go to html and back, then peel off extra wrappers
 51                     // 获取左边第一个标签元素
 52                     tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
 53                     
 54                     // 获取最外层元素的包裹元素,并将元素包裹在其中
 55                     wrap = wrapMap[ tag ] || wrapMap._default;
 56                     depth = wrap[0];
 57                     div.innerHTML = wrap[1] + elem + wrap[2];
 58                     
 59                     // Move to the right depth
 60                     // 如果元素的包裹深度大于1,div重新赋值为元素最近的包裹元素(即:包含第一层包裹元素)
 61                     while ( depth-- ) {
 62                         div = div.lastChild;
 63                     }
 64 
 65                     // Remove IE's autoinserted <tbody> from table fragments
 66                     // 在IE6,7中,清除字符串中空table标签中自动加入的tbody标签(手动加入的除外)
 67                     if ( !jQuery.support.tbody ) {
 68                         
 69                         // String was a <table>, *may* have spurious(伪造的) <tbody>
 70                         // 判断字符串中是否拥有空tbody标签
 71                         hasBody = rtbody.test(elem);
 72                         
 73                         // 如果最外层标签为table且table中没有手动加入tbody
 74                         // 变量tbody为div.firstChild.childNodes(自动加入的tbody标签集合)
 75                         tbody = tag === "table" && !hasBody ?
 76                             div.firstChild && div.firstChild.childNodes :
 77 
 78                             // String was a bare <thead> or <tfoot>
 79                             // 如果字符串中仅有一个空thead或tfoot标签
 80                             // 变量tbody为div.childNodes(字符串中的thead和tfoot标签集合)
 81                             wrap[1] === "<table>" && !hasBody ?
 82                                 div.childNodes :
 83                                 [];
 84 
 85                         for ( j = tbody.length - 1; j >= 0 ; --j ) {
 86 
 87                             // 排除thead或tfoot标签
 88                             if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
 89 
 90                                 // 清除空table标签中自动加入的tbody
 91                                 tbody[ j ].parentNode.removeChild( tbody[ j ] );
 92                             }
 93                         }
 94                     }
 95 
 96                     // IE completely kills leading whitespace when innerHTML is used
 97                     // 在ie9以下浏览器中,字符串以空白字符串开头,将空白字符串作为div元素的第一个文本子节点
 98                     if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
 99                         div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
100                     }
101 
102                     // 获取已经处理完毕的div子节点集合(nodeList对象)
103                     elem = div.childNodes;
104 
105                     // Take out of fragment container (we need a fresh div each time)
106                     // 在下一次循环处理字符串数组项前,清除处理创建过的div元素
107                     div.parentNode.removeChild( div );
108                 }
109             }
110 
111             // 如果elem为DOM节点(文本节点)
112             if ( elem.nodeType ) {
113                 ret.push( elem );
114             }
115             // 将nodeList对象中节点合并到返回的数组中
116             else {
117                 jQuery.merge( ret, elem );
118             }
119         }
120 
121         // Fix #11356: Clear elements from safeFragment
122         if ( div ) {
123             elem = div = safe = null;
124         }
125 
126         // Reset defaultChecked for any radios and checkboxes
127         // about to be appended to the DOM in IE 6/7 (#8060)
128         // 在ie6,7中,拥有checked属性的单选按钮,复选框在插入到其他标签后,选中状态会失效(下面代码修复该bug)
129         if ( !jQuery.support.appendChecked ) {
130             for ( i = 0; (elem = ret[i]) != null; i++ ) {
131                 if ( jQuery.nodeName( elem, "input" ) ) {
132                     fixDefaultChecked( elem );
133                 } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
134                     jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
135                 }
136             }
137         }
138 
139         // Append elements to a provided document fragment
140         // 将ret数组中的各DOM节点插入到提供的文档碎片中
141         // 提取dom节点中的script节点,并添加到ret数组中,位置为其原父元素索引位置后
142         if ( fragment ) {
143             // Special handling of each script element
144             handleScript = function( elem ) {
145                 // Check if we consider it executable
146                 // 如果elem元素不存在type属性或者type值为javascript或者为ecmascript
147                 if ( !elem.type || rscriptType.test( elem.type ) ) {
148                     // Detach the script and store it in the scripts array (if provided) or the fragment
149                     // Return truthy to indicate that it has been handled
150                     return scripts ?
151                         scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
152                         fragment.appendChild( elem );
153                 }
154             };
155 
156             for ( i = 0; (elem = ret[i]) != null; i++ ) {
157                 // Check if we're done after handling an executable script
158                 if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
159 
160                     // Append to fragment and handle embedded scripts
161                     // 将elem元素添加到文档碎片中并处理嵌入的脚本(script标签元素)
162                     fragment.appendChild( elem );
163                     if ( typeof elem.getElementsByTagName !== "undefined" ) {
164                         // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
165                         jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
166 
167                         // Splice the scripts into ret after their former ancestor and advance our index beyond them
168                         // 将script标签添加到数组,位置为其原父元素索引位置后
169                         ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
170                         i += jsTags.length;
171                     }
172                 }
173             }
174         }
175 
176         return ret;
177     }
178 });

转载请注明转自博客园jQuery.clean源码分析

原文地址:https://www.cnblogs.com/yangjunhua/p/2847808.html