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源码分析