最近发现各大类库都能利用div.innerHTML=HTML片断来生成节点元素,再把它们插入到目标元素的各个位置上。这东西实际上就是insertAdjacentHTML,但是IE可恶的innerHTML把这优势变成劣势。首先innerHTML会把里面的某些位置的空白去掉,见下面运行框的结果:
复制代码 代码如下:
<!doctype html>
<html dir="ltr" lang="zh-CN">
    <head>
        <meta charset="utf-8" />
        <title>
            IE的innerHTML By 司徒正美
        </title>
        <script type="text/javascript">
            window.onload = function() {
                var div = document.createElement("div");
                div.innerHTML = "   <td>    <b>司徒</b>正美         </td>        "
                alert("|" + div.innerHTML + "|");
                var c = div.childNodes;
                alert("生成的节点个数  " + c.length);
                for(var i=0,n=c.length;i<n;i++){
                      alert(c[i].nodeType);
                      if(c[i].nodeType === 1){
                          alert(":: "+c[i].childNodes.length);
                      }
                }        
            }
        </script>
    </head>
    <body>
        <p>
        </p>
    </body>
</html>
另一个可恶的地方是,在IE中以下元素的innerHTML是只读的:col、 colgroup、frameset、html、 head、style、table、tbody、 tfoot、 thead、title 与 tr。为了收拾它们,Ext特意弄了个insertIntoTable。insertIntoTable就是利用DOM的insertBefore与appendChild来添加,情况基本同jQuery。不过jQuery是完全依赖这两个方法,Ext还使用了insertAdjacentHTML。为了提高效率,所有类库都不约而同地使用了文档碎片。基本流程都是通过div.innerHTML提取出节点,然后转移到文档碎片上,然后用insertBefore与appendChild插入节点。对于火狐,Ext还使用了createContextualFragment解析文本,直接插入其目标位置上。显然,Ext的比jQuery是快许多的。不过jQuery的插入的不单是HTML片断,还有各种节点与jQuery对象。下面重温一下jQuery的工作流程吧。
复制代码 代码如下:
append: function() { 
  //传入arguments对象,true为要对表格进行特殊处理,回调函数 
  return this.domManip(arguments, true, function(elem){ 
    if (this.nodeType == 1) 
      this.appendChild( elem ); 
  }); 
}, 
domManip: function( args, table, callback ) { 
  if ( this[0] ) {//如果存在元素节点 
    var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), 
    //注意这里是传入三个参数 
    scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), 
    first = fragment.firstChild; 
    if ( first ) 
      for ( var i = 0, l = this.length; i < l; i++ ) 
        callback.call( root(this[i], first), this.length > 1 || i > 0 ? 
      fragment.cloneNode(true) : fragment ); 
    if ( scripts ) 
      jQuery.each( scripts, evalScript ); 
  } 
  return this; 
  function root( elem, cur ) { 
    return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? 
      (elem.getElementsByTagName("tbody")[0] || 
      elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 
      elem; 
  } 
} 
//elems为arguments对象,context为document对象,fragment为空的文档碎片 
clean: function( elems, context, fragment ) { 
  context = context || document; 
  // !context.createElement fails in IE with an error but returns typeof 'object' 
  if ( typeof context.createElement === "undefined" ) 
  //确保context为文档对象 
    context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 
  // If a single string is passed in and it's a single tag 
  // just do a createElement and skip the rest 
  //如果文档对象里面只有一个标签,如<div> 
  //我们大概可能是在外面这样调用它$(this).append("<div>") 
  //这时就直接把它里面的元素名取出来,用document.createElement("div")创建后放进数组返回 
  if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { 
    var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); 
    if ( match ) 
      return [ context.createElement( match[1] ) ]; 
  } 
  //利用一个div的innerHTML创建众节点 
  var ret = [], scripts = [], div = context.createElement("div"); 
  //如果我们是在外面这样添加$(this).append("<td>表格1</td>","<td>表格1</td>","<td>表格1</td>") 
  //jQuery.each按它的第四种支分方式(没有参数,有length)遍历aguments对象,callback.call( value, i, value ) 
  jQuery.each(elems, function(i, elem){//i为索引,elem为arguments对象里的元素 
    if ( typeof elem === "number" ) 
      elem += ''; 
    if ( !elem ) 
      return; 
    // Convert html string into DOM nodes 
    if ( typeof elem === "string" ) { 
      // Fix "XHTML"-style tags in all browsers 
      elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 
        return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 
          all : 
          front + "></" + tag + ">"; 
      }); 
      // Trim whitespace, otherwise indexOf won't work as expected 
      var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); 
      var wrap = 
        // option or optgroup 
        !tags.indexOf("<opt") && 
        [ 1, "<select multiple='multiple'>", "</select>" ] || 
        !tags.indexOf("<leg") && 
        [ 1, "<fieldset>", "</fieldset>" ] || 
        tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 
        [ 1, "<table>", "</table>" ] || 
        !tags.indexOf("<tr") && 
        [ 2, "<table><tbody>", "</tbody></table>" ] || 
        // <thead> matched above 
      (!tags.indexOf("<td") || !tags.indexOf("<th")) && 
        [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] || 
        !tags.indexOf("<col") && 
        [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] || 
        // IE can't serialize <link> and <script> tags normally 
        !jQuery.support.htmlSerialize &&//用于创建link元素 
      [ 1, "div<div>", "</div>" ] || 
        [ 0, "", "" ]; 
      // Go to html and back, then peel off extra wrappers 
      div.innerHTML = wrap[1] + elem + wrap[2];//比如"<table><tbody><tr>" +<td>表格1</td>+"</tr></tbody></table>" 
      // Move to the right depth 
      while ( wrap[0]-- ) 
        div = div.lastChild; 
      //处理IE自动插入tbody,如我们使用$('<thead></thead>')创建HTML片断,它应该返回 
      //'<thead></thead>',而IE会返回'<thead></thead><tbody></tbody>' 
      if ( !jQuery.support.tbody ) { 
        // String was a <table>, *may* have spurious <tbody> 
        var hasBody = /<tbody/i.test(elem), 
        tbody = !tags.indexOf("<table") && !hasBody ? 
          div.firstChild && div.firstChild.childNodes : 
          // String was a bare <thead> or <tfoot> 
        wrap[1] == "<table>" && !hasBody ? 
          div.childNodes : 
          []; 
        for ( var j = tbody.length - 1; j >= 0 ; --j ) 
        //如果是自动插入的里面肯定没有内容 
          if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) 
            tbody[ j ].parentNode.removeChild( tbody[ j ] ); 
      } 
      // IE completely kills leading whitespace when innerHTML is used 
      if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) ) 
        div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild ); 
     //把所有节点做成纯数组 
      elem = jQuery.makeArray( div.childNodes ); 
    } 
    if ( elem.nodeType ) 
      ret.push( elem ); 
    else
    //全并两个数组,merge方法会处理IE下object元素下消失了的param元素 
      ret = jQuery.merge( ret, elem ); 
  }); 
  if ( fragment ) { 
    for ( var i = 0; ret[i]; i++ ) { 
      //如果第一层的childNodes就有script元素节点,就用scripts把它们收集起来,供后面用globalEval动态执行 
      if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { 
        scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); 
      } else { 
        //遍历各层节点,收集script元素节点 
        if ( ret[i].nodeType === 1 ) 
          ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); 
        fragment.appendChild( ret[i] ); 
      } 
    } 
    return scripts;//由于动态插入是传入三个参数,因此这里就返回了 
  } 
  return ret; 
}, 

