zoukankan      html  css  js  c++  java
  • 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源码分析

  • 相关阅读:
    Linux下查看系统版本号信息的方法(转载)
    tomcat 启动超级慢
    新生代老年代GC组合
    GC 提前晋升
    Mysql 锁技术要点【转载】
    第39天:字符串连接、截取操作
    第38天:运算符、字符串对象常用方法
    第37天:小米手机图片展示
    第36天:倒计时:发动短信验证、跳转页面、关闭广告
    第35天:时钟效果
  • 原文地址:https://www.cnblogs.com/yangjunhua/p/2847808.html
Copyright © 2011-2022 走看看