DocumentFragment:它是什么以及如何(不)与之抗争

免责声明
似乎我正在开始撰写新的系列文章-有点无聊并且纯粹是功利主义者。 他们将解释经常给我的学生造成困难的要点。 如果您是经验丰富的Web开发人员,则很可能对您不感兴趣。 如果您正在等待星期五JS的强大功能的变态,可惜它们不会在这里。


学生经常难以理解的一件事是DocumentFragment。 一般来说,我不能怪他们。 具有外部简洁性,它具有一些不明显甚至违反直觉的属性。 在本文中,我希望收集初学者需要了解的所有有关他的知识。

图片

这是什么


DocumentFragment是可以包含任意数量的DOM元素的容器。 简单地说,您可以将其想象为一个存储桶。 元素堆叠在其中,以便在适当的时候立即将其立即丢弃。

如何建立


小学的。

var fragment = document.createDocumentFragment(); 

还有其他方法,但下面介绍它们。

我为什么需要


如我上面所写,为了存储DOM元素。 读者可能会反对:“但是它们可以存储在普通的唱片中。” 没错,该片段具有独特的属性,使其成为此角色的最佳人选。 考虑以下代码:

 var fragment = document.createDocumentFragment(); var parentDiv = document.createElement("div"); var div1 = document.createElement("div"); var div2 = document.createElement("div"); fragment.appendChild(div1); fragment.appendChild(div2); //   parentDiv.appendChild(fragment); console.log(parentDiv.children); 

控制台会告诉我们什么? 一个不熟悉DocumentFragment的人可能会认为parentDiv将有一个孩子fragment 。 但实际上,它将有两个子对象div1div2 。 事实是片段本身不是DOM元素,它只是DOM元素的容器。 并且当将它作为参数传递给诸如appendChildinsertBefore类的方法时,它不会嵌入DOM树中,而是将其内容嵌入在那里。

但是,为什么需要它呢?


“存储桶”属性当然是好的,但是在实践中这有什么用呢? DocumentFragment具有两个主要的应用领域。

1.存储没有共同祖先的HTML。

在某些情况下,我们需要替换元素的内容,但不要触摸元素本身。 假设我们使用事件委托,并且发生在内部元素上的所有事件处理程序都挂在外部div上。 在这种情况下,DocumentFragment非常适合我们:

 div.innerHTML = ""; div.appendChild(fragmentWithAllContent); 

“但是我们可以在创建元素时立即将它们添加到div吗?” -腐蚀性读者会问。 我们可以,但是不值得,这就是原因。

2.多次插入时的性能提高。

事实是,每次我们更改活动DOM树中的内容时,浏览器都必须进行大量计算。 例如,您可以在此处阅读有关此内容的更多信息。 在本文中,我们仅限于提及存在如此可怕的野兽-回流。 当我们在页面上添加元素时,这头野兽醒来并吞噬了一块处理器时间。 如果我们依次添加一百个元素,该野兽将被唤醒一百次,并被咬一口。 对于用户而言,这可能已经是非常明显的“冻结”。

当我们将一个元素添加到DocumentFragment时,这不会引起重排,因为该片段不是(并且基本上不能是)活动DOM树的一部分。 最重要的是:当我们使用appendChild或其他类似方法插入片段的内容时,无论片段中有多少元素, 回流仅被调用一次

为了清楚起见,我做了一个简单的基准测试,以便读者可以亲眼看到其中的区别。

Upd: Nuit同志 ,对于现代Chrome,我的话不再正确。 在其中,重排不会比必要的时间更早执行,并且由于这个原因,没有DocumentFragment的代码实际上可以更快地运行 ,而在其他浏览器中,它并不是那么明显。 因此,在决定是否使用片段之前,需要对网站的目标受众进行概要分析和研究。

细微差别


有两个功能使新手经常难以使用片段。 首先:如我上面所写,片段不是DOM元素 。 这意味着它缺少许多熟悉的方法和属性,尤其是innerHTML 。 因此,您不能仅将字符串转换为片段的内容。 如何做到这一点并不容易,将在下面描述。

第二个特点:使用“ spoils”时的片段。 更确切地说,它是空的。 当我们执行div.appendChild(fragment) ,该片段的所有子代都div.appendChild(fragment)div 。 而且由于一个元素不能有多个父元素,这意味着它们将从片段中删除! 为了避免这种情况,可以使用cloneNode

<template>标签


在一个地方,您可以不通过JS来创建DocumentFragment。 这是模板元素的content属性。

<template>专门为存储HTML代码<template>发明的,而不是提前加载浏览器。 此标记内的内容不会成为活动DOM树的一部分。 特别是(新手也经常遇到这种情况),无法使用querySelector找到它们。 从<template>标记内的HTML代码创建的元素不会成为其子代。 相反,JavaScript可以通过content属性访问它们,这真是令人惊讶! -只是DocumentFragment。

使用template元素,可以从字符串创建片段:

 function createFragmentFromString(str){ var template = document.createElement("template"); template.innerHTML = str; return template.content; } 

结语


如果您是Web开发的新手,希望您能学到很多。 如果您是一位经验丰富的开发人员,则可能需要在本文中添加一些内容,在这种情况下,请不要犹豫在评论中写些内容。 感谢您的阅读,并祝您愉快。

Source: https://habr.com/ru/post/zh-CN413287/


All Articles