JavaScript系统学习DOM系列文章之DocumentFragment
引言:为什么会把DocumentFragment与前端性能扯到一起,这是本篇文章给你的答案。
我们经常使用javascript来操作DOM元素,比如使用appendChild()方法。每次调用该方法时,浏览器都会重新渲染页面。如果大量的更新DOM节点,则会非常消耗性能,影响用户体验。
javascript提供了一个文档片段DocumentFragment的机制。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点。把所有要构造的节点都放在文档片段中执行,这样可以不影响文档树,也就不会造成页面渲染。当节点都构造完成后,再将文档片段对象添加到页面中,这时所有的节点都会一次性渲染出来,这样就能减少浏览器负担,提高页面渲染速度。
接下来,我们先认识下DocumentFragment的API,才能更好的帮助我们理解。
DocumentFragment类型
创建一个DocumentFragment类型的节点可以使用以下两种方法:
// 使用Document对象创建 var dfragment1 = document.createDocumentFragment(); // 使用构造函数创建 var dfragment2 = new DocumentFragment();
一个DocumentFragment类型的节点具有以下特征:
- nodeType值为11
- nodeName值为"#document-fragment"
- nodeValue值为null
- parentNode值为null
- 子节点可以是:Element、Comment、Text、ProcessingInstruction、CDATASection、EntityReference
不能直接把DocumentFragment类型添加到文档中,但可以将它做来一个仓库,用来存储可能会被添加到文档中的节点。
DocumentFragment类型介绍
DocumentFragment对象继承自Node,Node对象中所有的方法和属性都可以在DocumentFragment中使用,但DocumentFragment对象通常用于执行那些针对DOM的操作。
在浏览器所使用的一般是html或xhtml类型的文档,所以entities和notations属性都是一个空列表。
如,向以下ul列表表中插入子节点:
<ul id="myUl"></ul>
代码如下:
不使用DocumentFragment
var ul = document.getElementById("ul"); for (var i = 0; i < 20; i++) { var li = document.createElement("li"); li.innerHTML = "index: " + i; ul.appendChild(li); }
使用DocumentFragment
var dfragment = document.createDocumentFragment(); var ul = document.getElementById('myUl'); var li = null; for(var i=0; i<3; i++){ li = document.createElement('li'); li.appendChild(document.createTextNode('列表项:'+i)); dfragment.appendChild(li); } ul.appendChild(dfragment);
由于每一次对文档的插入都会引起重新渲染(计算元素的尺寸,显示背景,内容等),所以进行多次插入操作使得浏览器发生了很多次渲染,方式一效率是比较低的。这是我们提倡通过减少页面的渲染来提高DOM操作的效率的原因。
一个优化的方法是将要创建的元素写到一个字符串上,然后一次性写到innerHTML上,这种利用浏览器对innerHTML的解析确实是相比上面的多次插入快了很多。但是构造字符串灵活性上面比较差,很难符合创建各种各样的DOM元素的需求。
利用DocumentFragment,可以弥补这两个方法的不足。
DocumentFragment是没有父节点的最小的文档对象,用于存储HTML和XML片段。DocumentFragment对象继承Node,所以它有Node的所有属性方法,完全可以操作Node(NodeList)那样操作DocumentFragment。此外W3C对DocumentFragment也定义了一些另外的属性和方法,但是由于多数浏览器都没有实现,从兼容性上来说不推荐使用这些属性。具体有哪些属性方法可以参考MDN说明。
创建DocumentFragment的方法有两种,document.createDocumentFragment()和new Fragment()。对于document.createDocumentFragment(),所有浏览器都支持(包括IE6),而构造函数方法就不是所有浏览器都有效了(IE没有实现该方法)。所以从兼容性上来说推荐使用document.createDocumentFragment()。
上面也提到,使用DocumentFragment与一般的Node无异,可以当作是DOM对象一样操作。在使用appendChild,insertBefore等方法时,被添加(插入)的是片段的所有子节点,而非本身。
因为文档片段存在于内存中,并不在DOM中,所以将子元素插入到文档片段中时不会引起页面回流(对元素位置和几何上的计算),因此使用DocumentFragment可以起到性能优化的作用。
为什么推出DocumentFragment,我总结了DocumentFragment特殊之处:
1、DocumentFragment它并不属于DOM结构的一部分,所以任何对DocumentFragment的操作,不会影响到DOM
2、当我们给appendChild(), insertBefore(), replaceChild()等传入一个DocumentFragment的时候,是把DocumentFragment的所有子节点一次性地插入,而不是DocumentFragment本身
3、当我们把DocumentFragment插入到别的节点之后,DocumentFragment的子节点会自动被清空。
4、当我们把现有的DOM上的一个节点插入给DocumentFragment,这个节点会从原DOM上被删掉。
第一点和第二点主要是讲利用DocumentFragment来进行DOM操作在安全性和性能方面的优点。第三点也是它作为临时容器的一个优点,用完之后呢,会自己清空自己,不占内存。特别要注意的是第四点,还挺出乎我意料的,我就第四点来做一个实验:
<ul class='bookList'> <li class='bookItem'>book 1</li> <li class='bookItem'>book 2</li> </ul>
我们创建一个DocumentFragment,然后把第一个'<li>'给它做子节点:
var bookList = document.getElementsByClassName('bookList')[0]; var firstLi = bookList.firstElementChild; var df = document.createDocumentFragment(); df.appendChild(firstLi);
在执行了上面的代码之后,原来的HTML就变成了:
<ul class="bookList"> <li class="bookItem">book 2</li> </ul>
可以看到,我们把第一个'<li>'插入到了我们创建的DocumentFragment里面, 然后这个'<li>'就从原来的DOM里面消失了
写在最后
如果你正在学习JavaScript DOM这里,一定要仔细的看看这里,因为大公司的面试题里也藏着这道经典的题,如何优化大量dom操作?同时也有很多人疑问DocumentFragment 真的能提高 JS 动态添加 DOM 的性能吗?不要只道听途书,要知道具体为什么。
前端学习视频传送门
请先 后发表评论~