1

前言

笔记的内容主要是跟着慕课网上的jQuery源码解析系列课程以及自己的理解+实践来写的,可能会有错误,欢迎指出。当然,都是最先发在我的Segmentfault上的,所以要是看到了不要觉得奇怪(⊙o⊙)。

文档碎片

什么是文档碎片?

DocumentFragment是一个轻量级的文档对象,能够提取部分文档的树或创建一个新的文档片段,换句话说有文档缓存的作用。
它有以下性质:

  1. nodeType的值为11
  2. nodeName的值为“#document-fragment”
  3. nodeValue的值为 null
  4. parentNode的值为 null
  5. 子节点可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或 EntityReference

为什么要使用文档碎片:
牵涉到了重排(reflow)与重绘(repaint)的概念。简单来说,当元素的大小进行改变的时候,页面的布局就可能会改变,一个元素进行位置调整,这样影响到的可能是其他元素的位置都进行变化,这就是重排,而发生了重排就肯定会发生重绘啦(也就是上色啊之类的)。所以如果直接在JS代码中使用appendChild类似的一个个进行添加,其实效率很低下。如果使用文档碎片的话可以把要添加的一次性缓存起来,然后再一起添加上去。

创建方式:

var fragment = document.createDocumentFragment();

当把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment,而是它的所有子孙节点。

继续操作

需要做的事情:

  1. IE对字符串进行trimLeft操作。
  2. 很多标签不能直接作为DIV的子元素, td、th、tr、tfoot、tbody等等,需要加头尾。

例如:

<td>慕课网</td>

jQuery通过wrapMap转化成,否则有些会当成普通文本来解释:

"<table><tbody><tr><td>慕课网</td></tr></tbody></table>"

整个流程

  • 分解类型,jQuery对象,节点对象,文本,字符串,脚本 引入nodes收集各种分解的类型数据
  • 针对html节点,兼容IE的处理,先过滤空白,然后补全tr,td等 创建文档碎片的div包含节点,把html结构给innerHTML进去
  • 取出创建的节点,jQuery.merge(nodes, tmp.childNodes),因为靠div包装过
  //闭合标签检测
  var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;

正则表达的解析如下图所示:
图片描述

处理包裹,以及整合标签格式:

  var wrapMap = {
    //需要包裹处理的标签:[层数,被包裹代码]
    option: [1, "<select multiple='multiple'>", "</select>"],
    thead: [1, "<table>", "</table>"],
    col: [2, "<table><colgroup>", "</colgroup></table>"],
    tr: [2, "<table><tbody>", "</tbody></table>"],
    td: [3, "<table><tbody><tr>", "</tr></tbody></table>"],
    _default: [0, "", ""]
  };
  
  wrapMap.optgroup = wrapMap.option;
  wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
  wrapMap.th = wrapMap.td;

  /* 建立碎片
  * elems:数组,要添加的元素
  * context:被添加的容器 
  */
  function buildFragment(elems, context) {
    var elem, tmp, tag, wrap, contains, j,
        fragment = context.createDocumentFragment(),
        nodes = [],
        i = 0,
        length = elems.length;

    for (; i < length; i++) {
      elem = elems[i];

      if (elem || elem === 0) {
        // 如果是jQuery对象
        if (jQuery.type(elem) === "object") { 
        
          // 是普通元素对象加[elem],取出放入nodes数组中 
          jQuery.merge(nodes, elem.nodeType ? [elem] : elem);

        } else if (!/<|&#?\w+;/.test(elem)) {
          // 是一个文本节点,创建包裹后加入nodes
          nodes.push(context.createTextNode(elem));
        } else {
          // 创一个div做为容器
          tmp = tmp || fragment.appendChild(context.createElement("div"));
          
          //获取字符串标签名,若没有找到为""
          tag = (/<([\w:]+)/.exec(elem) || ["", ""])[1].toLowerCase();
          
          //用wrapMap的函数来获取标签的包裹(或者原路返回)
          wrap = wrapMap[tag] || wrapMap._default;
          
          /* 如果标签名不在闭合标签里,就替换成外包裹+<标签名></标签名>
          * $1是匹配的全部内容,$2是匹配的第一个内容,这里都会是那个标签名
          */
          tmp.innerHTML = wrap[1] + elem.replace(rxhtmlTag, "<$1></$2>") + wrap[2]; 

          // 因为warp被包装过,需要找到正确的元素父级
          j = wrap[0];
          while (j--) {
            tmp = tmp.lastChild;
          }
          // 把节点拷贝到nodes数组中去
          jQuery.merge(nodes, tmp.childNodes);
        }
      }
    }
    i = 0;
    while ((elem = nodes[i++])) {
      fragment.appendChild(elem)
    }
    return fragment;
  }

调用执行(没有模拟script执行)

 var $newdiv1 = $('<div id="object1"/>'),
     newdiv2 = document.createElement('div'),
     existingdiv1 = document.getElementById('foo');    

$('#test1').click(function() {
  "<table><tbody><tr><td>慕课网</td></tr></tbody></table>"
  $('body').append($newdiv1, [newdiv2, existingdiv1, '<td>慕课网</td>', '文本', '<script>alert(1)'])
})

$('#test2').click(function() {
  var fragment = buildFragment([$newdiv1, newdiv2, existingdiv1, '<td>慕课网</td>', '文本', '<script>alert(1)'], document)
  document.body.appendChild(fragment)
})


MOCHIKO
318 声望29 粉丝

引用和评论

0 条评论