前言
笔记的内容主要是跟着慕课网上的jQuery源码解析系列课程以及自己的理解+实践来写的,可能会有错误,欢迎指出。当然,都是最先发在我的Segmentfault上的,所以要是看到了不要觉得奇怪(⊙o⊙)。
文档碎片
什么是文档碎片?
DocumentFragment是一个轻量级的文档对象,能够提取部分文档的树或创建一个新的文档片段,换句话说有文档缓存的作用。
它有以下性质:
- nodeType的值为11
- nodeName的值为“#document-fragment”
- nodeValue的值为 null
- parentNode的值为 null
- 子节点可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或 EntityReference
为什么要使用文档碎片:
牵涉到了重排(reflow)与重绘(repaint)的概念。简单来说,当元素的大小进行改变的时候,页面的布局就可能会改变,一个元素进行位置调整,这样影响到的可能是其他元素的位置都进行变化,这就是重排,而发生了重排就肯定会发生重绘啦(也就是上色啊之类的)。所以如果直接在JS代码中使用appendChild类似的一个个进行添加,其实效率很低下。如果使用文档碎片的话可以把要添加的一次性缓存起来,然后再一起添加上去。
创建方式:
var fragment = document.createDocumentFragment();
当把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment,而是它的所有子孙节点。
继续操作
需要做的事情:
- IE对字符串进行trimLeft操作。
- 很多标签不能直接作为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)
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。