jQuery.clean源码分析包括: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标签 复制代码 代码如下: 创建一个div元素并插入到文档碎片中 处理xhtml风格标签 将elem包裹起来,并将包裹后的字符串作为div的innerHTML 如果包裹深度大于1,只留下第一层包裹元素 清除在ie6,7中空table标签自动加入的tbody 将在ie9以下浏览器中剔除的开头空白字符串作为div元素的第一个文本子节点 将elem重新赋值为div的子节点集合(nodeList对象), 移除本次循环中文档碎片中的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、函数中用到的变量及函数 复制代码 代码如下: var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], thead: [ 1, "<table>", "</table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, rtagName = /<([\w:]+)/, rtbody = /<tbody/i, rhtml = /<|?\w+;/, rleadingWhitespace = /^\s+/, rcheckableType = /^(?:checkbox|radio)$/, rscriptType = /\/(java|ecma)script/i; // 设置复选框checkbox或单选框radio表单元素的默认选中状态 function fixDefaultChecked( elem ) { if ( rcheckableType.test( elem.type ) ) { elem.defaultChecked = elem.checked; } } // 创建一个安全的文档碎片 function createSafeFragment( document ) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment(); // ie6,7,8浏览器把safeFrage作为HTMLDocument类型 // 针对ie9以下浏览器 if ( safeFrag.createElement ) { while ( list.length ) { safeFrag.createElement( list.pop() ); } } return safeFrag; } // 模拟ES5中Array的新功能 // 该函数API:http://www.css88.com/jqapi-1.8/#p=jQuery.grep jQuery.extend({ grep: function( elems, callback, inv ) { var retVal, ret = [], i = 0, length = elems.length; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; } });
2、源码分析 复制代码 代码如下: jQuery.extend({ clean: function( elems, context, fragment, scripts ) { // 声明变量 var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags, safe = context === document && safeFragment, ret = []; // 确保变量context为文档根节点document if ( !context || typeof context.createDocumentFragment === "undefined" ) { context = document; } // Use the already-created safe fragment if context permits for ( i = 0; (elem = elems[i]) != null; i++ ) { // 如果elem为数字,则将其转换为字符串 if ( typeof elem === "number" ) { elem += ""; } // 如果elem为undefined,跳出本次循环 if ( !elem ) { continue; } // Convert html string into DOM nodes // 转换数组项(字符串)为DOM节点 if ( typeof elem === "string" ) { // 如果不存在html实体编号或标签,则创建文本节点 if ( !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } // 处理是html标签字符串的数组项 else { // Ensure a safe container in which to render the html // safe为#document-fragment类型,在ie9以下浏览器中,safe为HTMLDocument类型节点,且nodeNames数组为空 safe = safe || createSafeFragment( context ); // 创建一个div元素并将其插入到文档碎片中 div = context.createElement("div"); safe.appendChild( div ); // Fix "XHTML"-style tags in all browsers // 除了area,br,col,embed,hr,img,input,link,meta,param这些标签外, // 将开始标签末尾加入斜杠的标签转换为开始和结束标签 elem = elem.replace(rxhtmlTag, "<$1></$2>"); // Go to html and back, then peel off extra wrappers // 获取左边第一个标签元素 tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); // 获取最外层元素的包裹元素,并将元素包裹在其中 wrap = wrapMap[ tag ] || wrapMap._default; depth = wrap[0]; div.innerHTML = wrap[1] + elem + wrap[2]; // Move to the right depth // 如果元素的包裹深度大于1,div重新赋值为元素最近的包裹元素(即:包含第一层包裹元素) while ( depth-- ) { div = div.lastChild; } // Remove IE's autoinserted <tbody> from table fragments // 在IE6,7中,清除字符串中空table标签中自动加入的tbody标签(手动加入的除外) if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious(伪造的) <tbody> // 判断字符串中是否拥有空tbody标签 hasBody = rtbody.test(elem); // 如果最外层标签为table且table中没有手动加入tbody // 变量tbody为div.firstChild.childNodes(自动加入的tbody标签集合) tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : // String was a bare <thead> or <tfoot> // 如果字符串中仅有一个空thead或tfoot标签 // 变量tbody为div.childNodes(字符串中的thead和tfoot标签集合) wrap[1] === "<table>" && !hasBody ? div.childNodes : []; for ( j = tbody.length - 1; j >= 0 ; --j ) { // 排除thead或tfoot标签 if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { // 清除空table标签中自动加入的tbody tbody[ j ].parentNode.removeChild( tbody[ j ] ); } } } // IE completely kills leading whitespace when innerHTML is used // 在ie9以下浏览器中,字符串以空白字符串开头,将空白字符串作为div元素的第一个文本子节点 if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); } // 获取已经处理完毕的div子节点集合(nodeList对象) elem = div.childNodes; // Take out of fragment container (we need a fresh div each time) // 在下一次循环处理字符串数组项前,清除处理创建过的div元素 div.parentNode.removeChild( div ); } } // 如果elem为DOM节点(文本节点) if ( elem.nodeType ) { ret.push( elem ); } // 将nodeList对象中节点合并到返回的数组中 else { jQuery.merge( ret, elem ); } } // Fix #11356: Clear elements from safeFragment if ( div ) { elem = div = safe = null; } // Reset defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) // 在ie6,7中,拥有checked属性的单选按钮,复选框在插入到其他标签后,选中状态会失效(下面代码修复该bug) if ( !jQuery.support.appendChecked ) { for ( i = 0; (elem = ret[i]) != null; i++ ) { if ( jQuery.nodeName( elem, "input" ) ) { fixDefaultChecked( elem ); } else if ( typeof elem.getElementsByTagName !== "undefined" ) { jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); } } } // Append elements to a provided document fragment // 将ret数组中的各DOM节点插入到提供的文档碎片中 // 提取dom节点中的script节点,并添加到ret数组中,位置为其原父元素索引位置后 if ( fragment ) { // Special handling of each script element handleScript = function( elem ) { // Check if we consider it executable // 如果elem元素不存在type属性或者type值为javascript或者为ecmascript if ( !elem.type || rscriptType.test( elem.type ) ) { // Detach the script and store it in the scripts array (if provided) or the fragment // Return truthy to indicate that it has been handled return scripts ? scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : fragment.appendChild( elem ); } }; for ( i = 0; (elem = ret[i]) != null; i++ ) { // Check if we're done after handling an executable script if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { // Append to fragment and handle embedded scripts // 将elem元素添加到文档碎片中并处理嵌入的脚本(script标签元素) fragment.appendChild( elem ); if ( typeof elem.getElementsByTagName !== "undefined" ) { // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); // Splice the scripts into ret after their former ancestor and advance our index beyond them // 将script标签添加到数组,位置为其原父元素索引位置后 ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); i += jsTags.length; } } } } return ret; } });
|