面试官:谈谈createElement与createDocumentFragment的区别?

3,356 阅读3分钟

createDocumentFragmentDOM 节点。 它们不是主 DOM 树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

方法描述
document.createElement(tagname)创建标签名为tagname的节点
document.createDocumentFragment()创建文档碎片节点

主要区别

nodeType

  • createElement 创建的是元素节点,节点类型(nodeType)为 1
  • createDocumentFragment 创建的是文档碎片,节点类型(nodeType)为 11
var e1 = document.createElement('div');
console.log(e1.nodeType); // 1

var e2 = document.createDocumentFragment();
console.log(e2.nodeType); // 11	

innerHTML

  • createElement 创建的元素可以直接使用 innerHTML 添加子元素。
  • createDocumentFragment 创建的文档碎片不能直接使用 innerHTML 添加子元素,只会变成一个普通属性。(能通过间接的方式添加,下面会提到)
var e1 = document.createElement('div');
e1.innerHTML = '<h1>橙某人</h1>';
document.body.appendChild(e1);
console.log(e1);

var e2 = document.createDocumentFragment();
e2.innerHTML = '<h1>橙某人</h1>';
document.body.appendChild(e2);
console.log(e2);
console.log(e2.innerHTML); // <h1>橙某人</h1>

页面只会存在 createElement 创建的元素。

image.png

appendChild

  • createElement 使用 appendChild 追加子元素时,如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild 只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点);如果把它追加进页面中,则插入的是它本身和它的所有子孙节点;即便它已经添加进了页面,我们依旧能继续重复操作。
// html
<body>
    <h1 id="h1">橙某人</h1>
</body>
// js
var e1 = document.createElement('div');
var h1 = document.getElementById('h1');
e1.appendChild(h1); // 页面<h1/>显示会消失
document.body.appendChild(e1); // 整个追加进页面中显示
e1.appendChild(document.createElement('h2')); // 继续重复操作

image.png

  • createDocumentFragment 使用 appendChild 追加子元素时,会把页面中的原来存在的元素删除;如果把它追加进页面中,则插入的不是 DocumentFragment 自身,而是它的所有子孙节点;如果它已经添加进了页面,我们就不能继续操作,它属于一次性操作。
// html
<body>
    <h1 id="h1">橙某人</h1>
</body>
// js
var e2 = document.createDocumentFragment();
var h1 = document.getElementById('h1');
e2.appendChild(h1); // 页面<h1/>显示会消失
document.body.appendChild(e2); // 子孙节点会在页面中显示
e2.appendChild(document.createElement('h2')); // 无效

image.png

返回值

  • 二者使用 appendChild 追加子元素后返回值都是新添加的子元素。
var e1 = document.createElement('div');
var c1 = e1.appendChild(document.createElement('p'));
console.log(c1); // <p></p>

var e2 = document.createDocumentFragment();
var c2 = e2.appendChild(document.createElement('p'));
console.log(c2); // <p></p>

借助返回值通过 innerHTML 间接给 createDocumentFragment 创建的文档碎片直接添加子元素:

var e2 = document.createDocumentFragment();
var c2 = e2.appendChild(document.createElement('p'))
c2.innerHTML = '<span></span>';
var res = document.body.appendChild(e2);

image.png

场景

上面简单的列举了两者的作用、区别,对于 createElement 相信大家都比较熟了,那么对于 createDocumentFragment 可能还有很多小伙伴还是比较陌生的,我们应该在何时、什么场景下使用它呢?下面我们通过一个小例子来看看吧。

假设我们想在 ul 节点下通过 JS 添加十个 li 节点应该怎么做呢?

<ul id="ul"></ul>

最常见、简单的操作:

var ul = document.getElementById('ul');
for(var i = 0;i<10;i++) {
    var li = document.createElement('li');
    ul.appendChild(li); // 重复操作DOM
}

不过对于一些对浏览器工作流程熟知的小伙伴来说,这代码是有问题的,由于每一次对页面插入元素都会引起页面的重新渲染,进行多次插入操作使得浏览器发生了很多次渲染,效率是比较低的。说白了会导致页面 重绘回流,这会带来页面的性能问题。

既然多次插入元素会有问题,那我们很容易就会想到,我们能先创建一个 div 节点,在这个节点进行插入操作后,再统一把它插入 ul 下:

var ul = document.getElementById('ul');
var div = document.createElement('div');
for(var i = 0;i<10;i++) {
    var li = document.createElement('li');
    div.appendChild(li);
}
ul.appendChild(div);

这确实回避了反复操作 DOM 的问题,但也带来了新的问题,就是 ul 下的 li 节点是包裹在 div 节点中的,这不是我们所期待的,这个时候就轮到 createDocumentFragment 出场了,前面我们也说过,它插入页面中,插入的不是 DocumentFragment 自身,而是它的所有子孙节点,刚好能利用这点特性:

var ul = document.getElementById('ul');
var f = document.createDocumentFragment();
for(var i = 0;i<10;i++) {
    var li = document.createElement('li');
    f.appendChild(li);
}
ul.appendChild(f);

至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。