假设我们希望在网页初始加载后添加一些内容。JavaScript 为我们提供了多种工具。也许您已经使用过其中一些工具,例如 append
、appendChild
、insertAdjacentHTML
或 innerHTML
。
使用 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 元素。
- 节点要么使用 JavaScript 中的
document.createElement()
创建,要么使用上一节中介绍的查询方法之一选择。 - 纯文本就是文本。它是纯文本,因为它不包含任何 HTML 标签或格式化内容(例如
Hello
)。 - HTML 也是文本,但与纯文本不同的是,它确实会在添加到 DOM 时被解析为标记(例如
<div>Hello</div>
)。
列出每种方法支持的参数可能会有所帮助
方法 | 节点 | HTML 文本 | 文本 |
---|---|---|---|
append | 是 | 否 | 是 |
appendChild | 是 | 否 | 否 |
insertAdjacentHTML | 否 | 是 | 是1 |
innerHTML 2 | 否 | 是 | 是 |
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 代码可能会变得非常繁琐。如果我们使用 append
或 appendChild
,就无法避免这一点。
在这种大量标记的情况下,最好将我们的 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 | 是 | 否 | 否 | 是 | 保留 | 是 | 中等 |
insertAdjacentHTML | 否 | 是 | 是1 | 是 | 保留 | 小心 | 简单 |
innerHTML 2 | 否 | 是 | 是 | 是 | 丢失 | 小心 | 简单 |
insertAdjacentText
是推荐的。2
innerHTML
不是使用传统参数,而是像这样使用:element.innerHTML = 'HTML String'
如果要将所有内容浓缩成几条建议
- 不建议使用
innerHTML
进行追加,因为它会删除事件监听器。 - 如果你喜欢使用节点元素或纯文本的灵活性,并且不需要支持 Internet Explorer,
append
非常有效。 - 如果你喜欢(或需要)使用节点元素,并且想要全面的浏览器覆盖率,
appendChild
非常有效。 - 如果你需要生成 HTML,并且想要对 HTML 在 DOM 中放置的位置进行更具体的控制,
insertAdjacentHTML
非常不错。
深入了解
上面讨论的方法是常用的,应该涵盖你的大多数用例。
也就是说,如果你感兴趣,还有一些额外的追加/插入方法
最后的想法和一个小广告 :)
这篇文章的灵感来自于我最近在构建聊天应用程序时遇到的实际问题。正如你想象的那样,聊天应用程序依赖于大量的追加/插入——人们上线、新消息、通知等等。
那个聊天应用程序叫做Bounce。它是一个点对点学习聊天。假设你是 JavaScript 开发人员(除其他外),你可能有一些东西可以教!你也可以赚取一些额外的收入。
如果你好奇,这里是主页的链接,或者我在 Bounce 上的个人资料。干杯!
不错的文章。不过,请允许我提出一些意见。
应该是
document.getElementsByClassName
而不是document.getElementbyClass
。我不确定为什么你在这段代码中使用括号
new_link.className = ('def')
。你能解释一下吗?嗨,Konstantin,感谢你的意见。是的,你在两点上都说得对——文章已经更新了。
我认为你遗漏了一个非常重要的函数,它鲜为人知,那就是 Document.createDocumentFragment()。我们可以使用这个函数(而不是 document.createElement)将 HTML 代码转换为解析后的 HTML 代码,然后追加创建的片段。性能也更高,因为它占用更少的内存。因此
const element = Document.createDocumentFragment(html_string);
el_parent.append(element);
你怎么看?
Pau Sabater
我只能推荐使用 innerHTML。
在我看来,构建一个临时的,即使非常大的字符串,然后将其应用于 innerHTML,速度非常快,即使是在移动设备上。
不过无法使用事件监听器(正如作者所说)。
另外——insertBefore、before、after、replaceChildren、prepend。以及一次性插入节点的一般建议,以减少重绘。
是的,好的建议——可以将其视为一篇关于这些方法的性能特征的更长文章的一部分。
我已经注意到
insertBefore
、before
和after
——我已经添加了一个 “深入了解” 部分。除了其他评论者指出的内容之外,你还忘记了 insertAdjacentElement,这很奇怪,因为你提到了 insertAdjacentHTML 和 insertAdjacentText。
我还想提一下,在一次性插入多个同级节点时,可以使用文档片段……
我已经在 “深入了解” 部分添加了
insertAdjacentElement
,谢谢!关于文档片段的想法很好——比较性能特征和优化技巧会很有意思。
我也非常喜欢
.replaceWith()
!https://mdn.org.cn/en-US/docs/Web/API/Element/replaceWith
.replaceChild()
也不错,但有点笨拙,因为你必须获取三个不同节点的引用才能使用它。