比较使用 JavaScript 进行追加和插入的方法

Avatar of Eric Markfield
Eric Markfield

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

假设我们希望在网页初始加载后添加一些内容。JavaScript 为我们提供了多种工具。也许您已经使用过其中一些工具,例如 appendappendChildinsertAdjacentHTMLinnerHTML

使用 JavaScript 进行追加和插入内容的难点不在于它提供的工具,而在于选择使用哪种工具,何时使用以及了解每种工具的工作原理。

让我们尝试澄清这些问题。

超级简要背景

在深入研究之前,讨论一些背景信息可能会有所帮助。从最简单的层面上讲,网站是从服务器下载到浏览器的 HTML 文件。

您的浏览器将 HTML 文件中的 HTML 标签转换为可以使用 JavaScript 操作的一组对象。这些对象构建一个 文档对象模型 (DOM) 树。此树是一系列对象,这些对象以父子关系结构化。

在 DOM 语法中,这些对象被称为节点,更确切地说是 HTML 元素。

<!-- I'm the parent element -->
<div>
  <!-- I'm a child element -->
  <span>Hello</span>
</div>

在本例中,HTML span 元素是 div 元素的子元素,而 div 元素是父元素

我知道其中一些术语很奇怪,可能令人困惑。我们说“节点”,但有时我们会说“元素”或“对象”。而且,在某些情况下,它们指的是同一件事,只是取决于我们想要多具体。

例如,“元素”是“节点”的一种特定类型,就像苹果是水果的一种特定类型。

我们可以从最一般到最具体的顺序来组织这些术语:对象节点元素HTML 元素

了解这些 DOM 项目非常重要,因为我们将在页面初始加载后与它们进行交互,以使用 JavaScript 添加和追加内容。事实上,让我们开始做这件事。

设置

这些追加和插入方法大多遵循这种模式

Element.append_method_choice(stuff_to_append)

再次强调,元素仅仅是 DOM 树中的一个对象,它代表了一些 HTML。之前我们提到过 DOM 树的目的是为了让我们能够使用 JavaScript 方便地与 HTML 交互。

那么,我们如何使用 JavaScript 获取 HTML 元素呢?

查询 DOM

假设我们有以下一小段 HTML

<div id="example" class="group">
  Hello World
</div>

有几种常见的 DOM 查询方法

// Query a specific selector (could be class, ID, element type, or attribute):
const my_element1 = document.querySelector('#example')

// Query an element by its ID:
const my_element2 = document.getElementbyId('example')

// Query an element by its class:
const my_element3 = document.getElementsbyClassName('group')[0] 

在本例中,这三行都查询了相同的内容,但寻找方式不同。其中一个查找任何项目的 CSS 选择器;一个查找项目的 ID;一个查找项目的类。

请注意,getElementbyClass 方法返回一个数组。这是因为它能够匹配 DOM 中的多个元素,将这些匹配项存储在数组中可确保所有匹配项都被考虑在内。

我们可以追加和插入的内容

// Append Something
const my_element1 = document.querySelector('#example')
my_element1.append(something)

在本例中,something 是一个参数,代表我们想要附加到(即追加到)匹配元素末尾的内容。

我们不能将任何东西追加到任何对象。append 方法只允许我们将节点或纯文本追加到 DOM 中的元素。但其他一些方法也可以将 HTML 追加到 DOM 元素。

  1. 节点要么使用 JavaScript 中的 document.createElement() 创建,要么使用上一节中介绍的查询方法之一选择。
  2. 纯文本就是文本。它是纯文本,因为它不包含任何 HTML 标签或格式化内容(例如 Hello)。
  3. HTML 也是文本,但与纯文本不同的是,它确实会在添加到 DOM 时被解析为标记(例如 <div>Hello</div>)。

列出每种方法支持的参数可能会有所帮助

方法节点HTML 文本文本
append
appendChild
insertAdjacentHTML1
innerHTML2
1 这可以工作,但建议使用 insertAdjacentText
2 innerHTML 不是使用传统参数,而是像这样使用:element.innerHTML = 'HTML String'

如何选择使用哪种方法

嗯,这确实取决于您要追加的内容,以及需要解决的某些浏览器怪癖。

  • 如果您有发送到 JavaScript 的现有 HTML,那么使用支持 HTML 的方法可能是最容易的。
  • 如果您在 JavaScript 中构建一些新的 HTML,那么使用大量标记创建节点可能会很麻烦,而 HTML 则简洁得多。
  • 如果您希望立即附加事件监听器,则需要使用节点,因为我们在节点上调用 addEventListener,而不是 HTML。
  • 如果您只需要文本,那么任何支持纯文本参数的方法都可以。
  • 如果您的 HTML 可能不可信(即它来自用户输入,例如博客文章中的评论),那么在使用 HTML 时需要谨慎,除非它已被清理(即已删除有害代码)。
  • 如果您需要支持 Internet Explorer,则 不能使用 append

示例

假设我们有一个聊天应用程序,我们希望在用户 Dale 登录时将其追加到好友列表。

<!-- HTML Buddy List -->
<ul id="buddies">
  <li><a>Alex</a></li>
  <li><a>Barry</a></li>
  <li><a>Clive</a></li>
  <!-- Append next user here -->
</ul>

以下是使用上述每种方法实现此目的的方法。

append

我们需要创建一个节点对象,它转换为 <li><a>Dale</a></li>

const new_buddy = document.createElement('li')
const new_link = document.createElement('a')

const buddy_name = "Dale"

new_link.append(buddy_name) // Text param
new_buddy.append(new_link) // Node param

const list = document.querySelector('#buddies')
list.append(new_buddy) // Node param

我们最终的 append 将新用户放置在好友列表的末尾,就在结束标签 </ul> 之前。如果我们希望将用户放置在列表的开头,可以使用 prepend 方法。

您可能已经注意到,我们也可以使用 append 像这样用文本填充 <a> 标签

const buddy_name = "Dale"
new_link.append(buddy_name) // Text param

这突出了 append 的多功能性。

最后再提醒一下,append 在 Internet Explorer 中不受支持

appendChild

appendChild 是我们用于将内容追加到 DOM 元素的另一种 JavaScript 方法。它略有局限性,因为它只适用于节点对象,因此我们需要 textContent(或 innerText)来满足我们的纯文本需求。

请注意,与 append 不同,appendChild 在 Internet Explorer 中受支持

const new_buddy = document.createElement('li')
const new_link = document.createElement('a')

const buddy_name = "Dale"

new_link.textContent = buddy_name
new_buddy.appendChild(new_link) // Node param

const list = document.querySelector('#buddies')
list.appendChild(new_buddy) // Node param

在继续之前,让我们考虑一个类似的示例,但使用更复杂的标记。

假设我们想要追加的 HTML 不是像 <li><a>Dale</a></li> 这样的,而是

<li class="abc" data-tooltip="Click for Dale">
  <a id="user_123" class="def" data-user="dale">
    <img src="images/dale.jpg" alt="Profile Picture"/>
    <span>Dale</span>
  </a>
</li>

我们的 JavaScript 代码将会类似于

const buddy_name = "Dale"

const new_buddy = document.createElement('li')
new_buddy.className = 'abc'
new_buddy.setAttribute('data-tooltip', `Click for ${buddy_name}`)

const new_link = document.createElement('a')
new_link.id = 'user_123'
new_link.className = 'def'
new_link.setAttribute('data-user', buddy_name)

const new_profile_img = document.createElement('img')
new_profile_img.src = 'images/dale.jpg'
new_profile_img.alt = 'Profile Picture'

const new_buddy_span = document.createElement('span')
new_buddy_span.textContent = buddy_name

new_link.appendChild(new_profile_img) // Node param
new_link.appendChild(new_buddy_span) // Node param
new_buddy.appendChild(new_link) // Node param

const list = document.querySelector('#buddies')
list.appendChild(new_buddy) // Node param

没有必要完全遵循上面的 JavaScript 代码——重点是,在 JavaScript 中创建大量 HTML 代码可能会变得非常繁琐。如果我们使用 appendappendChild,就无法避免这一点。

在这种大量标记的情况下,最好将我们的 HTML 代码写成字符串,而不是使用一堆 JavaScript 方法……

insertAdjacentHTML

insertAdjacentHTML 类似于 append,因为它也能将内容添加到 DOM 元素中。不过,不同的是 insertAdjacentHTML 会将内容插入到与匹配元素相关的特定位置。

它恰好适用于 HTML。这意味着我们可以将实际的 HTML 插入到 DOM 元素中,并使用四种不同的位置精确地确定我们想要插入的位置

<!-- beforebegin -->
<div id="example" class="group">
  <!-- afterbegin -->
  Hello World
  <!-- beforeend -->
</div>
<!-- afterend -->

所以,我们可以通过将 HTML 插入到 #buddies 选择器的 beforeend 位置来复制 “追加” HTML 的相同想法

const buddy_name = "Dale"

const new_buddy = `<li><a>${buddy_name}</a></li>`
const list = document.querySelector('#buddies')
list.insertAdjacentHTML('beforeend', new_buddy)

请记住我们之前提到的安全问题。我们**永远**不希望插入最终用户提交的 HTML 代码,因为这会使我们面临跨站点脚本漏洞。

innerHTML

innerHTML 是另一种插入内容的方法。也就是说,它不建议用于插入,正如我们将看到的。

这是我们的查询和我们想要插入的 HTML 代码

const buddy_name = "Dale"
const new_buddy = `<li><a>${buddy_name}</a></li>`
const list = document.querySelector('#buddies')  
list.innerHTML += new_buddy

最初,这似乎有效。我们更新后的好友列表在 DOM 中看起来像这样

<ul id="buddies">
  <li><a>Alex</a></li>
  <li><a>Barry</a></li>
  <li><a>Clive</a></li>
  <li><a>Dale</a></li>
</ul>

这就是我们想要的!但是,使用 innerHTML 存在一个限制,它阻止我们在 #buddies 中的任何元素上使用事件监听器,因为 list.innerHTML += new_buddy 中的 += 的特性。

您会看到,A += B 的行为与 A = A + B 相同。在这种情况下,A 是我们现有的 HTML,B 是我们插入到其中的内容。问题是,这会导致现有 HTML 的副本,其中包含额外的插入 HTML。事件监听器无法监听副本。这意味着,如果我们想要监听好友列表中任何 <a> 标签的点击事件,我们将在使用 innerHTML 时失去这种能力。

所以,请注意这一点。

演示

这是一个演示,将我们已经涵盖的所有方法整合在一起。点击每个方法的按钮,都会在好友列表中插入 “Dale” 作为一项。

打开 DevTools,看看新的列表项是如何添加到 DOM 中的。

回顾

以下是对我们在将内容追加和插入到 DOM 时所处位置的概览。将其视为你在需要帮助确定使用哪种方法时的备忘单。

方法节点
HTML 文本文本
Internet Explorer?事件监听器安全?
HTML 模板
append保留中等
appendChild保留中等
insertAdjacentHTML1保留小心简单
innerHTML2丢失小心简单
1 这有效,但 insertAdjacentText推荐的。
2 innerHTML 不是使用传统参数,而是像这样使用:element.innerHTML = 'HTML String'

如果要将所有内容浓缩成几条建议

  • 不建议使用 innerHTML 进行追加,因为它会删除事件监听器。
  • 如果你喜欢使用节点元素或纯文本的灵活性,并且不需要支持 Internet Explorer,append 非常有效。
  • 如果你喜欢(或需要)使用节点元素,并且想要全面的浏览器覆盖率,appendChild 非常有效。
  • 如果你需要生成 HTML,并且想要对 HTML 在 DOM 中放置的位置进行更具体的控制,insertAdjacentHTML 非常不错。

深入了解

上面讨论的方法是常用的,应该涵盖你的大多数用例。

也就是说,如果你感兴趣,还有一些额外的追加/插入方法

最后的想法和一个小广告 :)

这篇文章的灵感来自于我最近在构建聊天应用程序时遇到的实际问题。正如你想象的那样,聊天应用程序依赖于大量的追加/插入——人们上线、新消息、通知等等。

那个聊天应用程序叫做Bounce。它是一个点对点学习聊天。假设你是 JavaScript 开发人员(除其他外),你可能有一些东西可以教!你也可以赚取一些额外的收入。

如果你好奇,这里是主页的链接,或者我在 Bounce 上的个人资料。干杯!