一般动态创建html元素都是创建好了直接appendChild()上去,但是如果要添加大量的元素还用这个方法的话就会导致大量的重绘以及回流,所以需要一个'缓存区'来保存创建的节点,然后再一次性添加到父节点中。这时候DocumentFragment对象就派上用场了。 createDocumentFragment有什么作用呢?
调用多次document.body.append(),每次都要刷新页面一次。效率也就大打折扣了,而使用document_createDocumentFragment()创建一个文档碎片,把所有的新结点附加在其上,然后把文档碎片的内容一次性添加到document中,这也就只需要一次页面刷新就可。
他支持以下DOM2方法:
a, cloneNode, hasAttributes, hasChildNodes, insertBefore, normalize, removeChild, replaceChild.
也支持以下DOM2属性:
attributes, childNodes, firstChild, lastChild, localName, namespaceURI, nextSibling, nodeName, nodeType, nodeValue, ownerDocument, parentNode, prefix, previousSibling, textContent.
在《javascript高级程序设计》一书的6.3.5:创建和操作节点一节中,介绍了几种动态创建html节点的方法,其中有以下几种常见方法:
crateAttribute(name):用指定名称name创建特性节点
createComment(text):创建带文本text的注释节点
createDocumentFragment():创建文档碎片节点
createElement(tagname):创建标签名为tagname的节点
createTextNode(text):创建包含文本text的文本节点
复制代码
在所有节点类型中,只有文档片段节点DocumentFragment在文档中没有对应的标记。DOM规定文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源
看下w3c的官方说明:
DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。
不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。
重点就在于DocumentFragment 节点不属于文档树。因此当把创建的节点添加到该对象时,并不会导致页面的回流,因此性能就自然上去了。
创建文档片段,要使用document.createDocumentFragment()方法。文档片段继承了Node的所有方法,通常用于执行那些针对文档的DOM操作
文档片段节点的三个node属性——nodeType、nodeName、nodeValue分别是11、'#document-fragment'和null,文档片段节点没有父节点parentNode
<div id="test">JavaScript</div>
const elementNode = document.getElementById('test')
const attrNode = elementNode.getAttributeNode('id')
const textNode = elementNode.firstChild
console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType) // 1 2 3
复制代码
var frag = document.createDocumentFragment();
console.log(frag.nodeType);//11
console.log(frag.nodeValue);//null
console.log(frag.nodeName);//'#document-fragment'
console.log(frag.parentNode);//null
var fragment = document.createDocumentFragment();
<script type="text/javascript">
var pNode,fragment = document.createDocumentFragment();
for(var i=0; i<20; i++){
pNode = document.createElement('p');
pNode.innerHTML = i;
fragment.appendChild(pNode);
}
document.body.appendChild(fragment);
</script>
复制代码
作用
我们经常使用javascript来操作DOM元素,比如使用appendChild()方法。每次调用该方法时,浏览器都会重新渲染页面。如果大量的更新DOM节点,则会非常消耗性能,影响用户体验
javascript提供了一个文档片段DocumentFragment的机制。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点。把所有要构造的节点都放在文档片段中执行,这样可以不影响文档树,也就不会造成页面渲染。当节点都构造完成后,再将文档片段对象添加到页面中,这时所有的节点都会一次性渲染出来,这样就能减少浏览器负担,提高页面渲染速度
<ul id="list1"></ul>
<script>
var list1 = document.getElementById('list1');
console.time("time");
var fragment = document.createDocumentFragment();
for(var i = 0; i < 500000; i++){
fragment.appendChild(document.createElement('li'));
}
list1.appendChild(fragment);
console.timeEnd('time');
</script>
<ul id="list"></ul>
<script>
var list = document.getElementById('list');
console.time("time");
for(var i = 0; i < 500000; i++){
list.appendChild(document.createElement('li'));
}
console.timeEnd('time');
</script>
复制代码
document.createDocumentFragment()说白了就是为了节约使用DOM。每次JavaScript对DOM的操作都会改变页面的变现,并重新刷新整个页面,从而消耗了大量的时间。为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次性添加到document中。
由于文档片段的优点在IE浏览器下并不明显,反而可能成为多此一举。所以,该类型的节点并不常用
DEMO
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="main1"></div>
<div id="main2"></div>
</body>
<script type="text/javascript">
var d1 = new Date();
//创建十个段落,常规的方式
for(var i = 0; i < 1000; i++) {
var p = document.createElement("p");
var oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
document.getElementById("main1").appendChild(p);
}
var d2 = new Date();
console.log("第一次创建需要的时间:" + (d2.getTime() - d1.getTime()));
//使用了createDocumentFragment()的程序
var d3 = new Date();
var pFragment = document.createDocumentFragment();
for(var i = 0; i < 1000; i++) {
var p = document.createElement("p");
var oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
pFragment.appendChild(p);
}
document.getElementById("main2").appendChild(pFragment);
var d4 = new Date();
console.log("第2次创建需要的时间:" + (d4.getTime() - d3.getTime()));
</script>
</html>
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="test">Node</div>
<div id="app">
<ul>
<li>test1</li>
<li>test2</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
</ul>
</div>
<script type="text/javascript">
//6. DocumentFragment: 文档碎片(高效批量更新多个节点)
// document: 对应显示的页面, 包含n个elment 一旦更新document内部的某个元素界面更新
// documentFragment: 内存中保存n个element的容器对象(不与界面关联), 如果更新framgnet中的某个element, 界面不变
/*
<div id="app">
<ul>
<li>test1</li>
<li>test2</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
<li>test3</li>
</ul>
</div>
*/
const div = document.getElementById('app')
// 1. 创建fragment
const fragment = document.createDocumentFragment()
// 2. 取出ul中所有子节点取出保存到fragment
let child
while(child=div.firstChild) { // 一个节点只能有一个父亲
fragment.appendChild(child) // 先将child从ul中移除, 添加为fragment子节点
}
// 3. 更新fragment中所有li的文本
Array.prototype.slice.call(fragment.childNodes).forEach(node => {
if (node.nodeType===1) { // 元素节点 <ul>
Array.from(node.children).forEach(li => li.textContent = '哈哈')
}
})
// 4. 将fragment插入ul
div.appendChild(fragment)
</script>
</body>
</html>
复制代码