- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
导读
本篇基于 Modern JavaScript Tutorial(现代 JavaScript 教程)仓库中 "Insert the HTML in the list" 练习任务,讲解如何在两个既有<li>之间插入一段 HTML,并借此深入掌握 DOM 修改的核心工具之一——insertAdjacentHTML。你将学会四个插入位置参数的区别、它与append/before/after等现代插入方法的取舍,以及"按 HTML 插入"与"按文本插入"在安全性上的本质差异,能够直接应用于动态列表、菜单、消息提示等页面渲染场景。
任务还原:在列表中间插入 HTML
原任务(task.md)给出的 HTML 结构如下:
<ul id="ul"> <li id="one">1</li> <li id="two">4</li> </ul>任务要求编写代码,把<li>2</li><li>3</li>插入到两个<li>之间,即最终得到:
<ul id="ul"> <li id="one">1</li> <li id="two">2</li> <li id="three">3</li> <li id="two">4</li> </ul>这个任务本身考察的是:当需要把一段结构化的 HTML 字符串(包含标签)插入到文档中某个相对位置时,应该选择哪个 DOM 方法。任务的答案与完整讲解位于同目录的 solution.md 与所属章节 article.md。
参考答案:insertAdjacentHTML('afterend', ...)
任务的官方参考解法只有一行:
one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');这行代码的含义是:以id="one"的<li>元素为基准,把 HTML 字符串插入到它之后(即<li id="two">4</li>之前)。由于<li>1</li>与<li>4</li>是相邻的兄弟节点,插入到第一个元素后面,恰好就落在两者之间。
为什么任务推荐insertAdjacentHTML而不是innerHTML拼接?核心原因是:insertAdjacentHTML只影响目标位置附近的节点,而innerHTML会整体重写整个父元素的内容。如果对ul.innerHTML做字符串拼接,需要先读出原有 HTML、手动拼接、再写回,既繁琐又容易破坏原本绑定在节点上的引用与事件。insertAdjacentHTML则把"在哪里插入"和"插入什么"两个问题清晰拆开。
深入理解:四个插入位置参数
elem.insertAdjacentHTML(where, html)的第一个参数where必须是四个关键字之一(详见 article.md 的 "insertAdjacentHTML/Text/Element" 一节):
| 位置关键字 | 插入效果 |
|---|---|
"beforebegin" | 插入到elem之前(与elem成为兄弟节点) |
"afterbegin" | 插入到elem内部、最前面(成为第一个子节点) |
"beforeend" | 插入到elem内部、最后面(成为最后一个子节点) |
"afterend" | 插入到elem之后(与elem成为兄弟节点) |
仓库中的 insert-adjacent.svg 用图示直观展示了这四种插入点:
其中afterbegin/beforeend等价于"在目标元素内部的首/尾"插入,而beforebegin/afterend则是"在目标元素外部的前/后"插入。回到任务场景:one.insertAdjacentHTML('afterend', ...)使用的是兄弟级插入点,因此不会触碰ul内部的其他子节点,也不会触发任何重新解析整个列表的开销。
结合示例理解四个位置的差异(示例同样来自 article.md):
<div id="div"></div> <script> div.insertAdjacentHTML('beforebegin', '<p>Hello</p>'); div.insertAdjacentHTML('afterend', '<p>Bye</p>'); </script>最终 DOM 为:
<p>Hello</p> <div id="div"></div> <p>Bye</p>为什么不用after()直接插入节点?
也许你会想到更现代的写法one.after(...)。它确实能实现同样的效果,但有一个关键区别:before/after/append/prepend会把字符串参数按"纯文本"插入,而不是按 HTML 解析。
以任务为例,如果用one.after('<li>2</li><li>3</li>'),浏览器不会创建两个<li>元素,而是插入一个包含尖括号字面量的文本节点——页面上会直接显示<li>2</li><li>3</li>这串字符,这正是 article.md 强调的:"strings are inserted in a safe way, likeelem.textContentdoes it"。
因此:
- 要插入DOM 节点或纯文本(希望自动转义),用
append/prepend/before/after; - 要插入一段可解析的 HTML 字符串(带标签、样式、结构),用
insertAdjacentHTML。
仓库中的对比图 before-prepend-append-after.svg 展示了现代插入方法的效果:
另外请注意:现代插入方法的参数可以是"任意多个节点或字符串"的列表,且字符串会自动转为文本节点。而insertAdjacentHTML的第二个参数始终是单个 HTML 字符串。
文本安全性与textContent的配合
insertAdjacentHTML的"按 HTML 解析"特性既是能力也是风险。当用户输入可能包含<b>、<script>等标签时,直接插入 HTML 会导致内容被当作标记执行或渲染。
仓库中两个相邻任务正好互为印证:
- createTextNode vs innerHTML vs textContent 任务 指出:
elem.append(document.createTextNode(text))与elem.textContent = text效果完全一致,都会把字符串作为纯文本放入元素;而elem.innerHTML = text会作为 HTML解析。二者结果截然不同,这正是"按文本插入"与"按 HTML 插入"的分水岭。 - Create a list 任务 要求"如果用户输入 HTML 标签,应将其按文本对待",其 参考实现 正是通过
li.textContent = data配合ul.append(li)完成动态列表构建——即先创建<li>元素,再用textContent安全赋值。
所以正确的选择标准是:
- 内容来源可信、可控(如模板字符串、服务端渲染好的片段)→ 可用
insertAdjacentHTML; - 内容来自用户输入或不可信来源→ 使用
textContent/createTextNode,或用createElement创建元素后逐个属性赋值,避免 XSS。
两个"兄弟"方法:insertAdjacentText与insertAdjacentElement
insertAdjacentHTML还有两个语法完全一致的兄弟方法(见 article.md):
elem.insertAdjacentText(where, text)—— 同样的四个位置关键字,但第二个参数按纯文本插入;elem.insertAdjacentElement(where, elem)—— 同样的四个位置关键字,但第二个参数是一个DOM 元素。
它们存在的意义主要是让插入 API 的语法保持"统一"。在实践中,大多数情况下只用insertAdjacentHTML,因为插入元素和文本时,append/prepend/before/after写起来更短、且天然支持多参数批量插入。例如 article.md 中的消息提示示例,就用insertAdjacentHTML("afterbegin", ...)把带样式的提示块插到页面顶部。
实战扩展:同类动态列表场景的三种写法
把任务的思想推广到"动态生成列表"场景,仓库给出了多种等价实现路径,可根据需求选择:
写法一:insertAdjacentHTML(字符串模板,适合内容可信时)
let ul = document.getElementById('ul'); for (let i = 2; i <= 3; i++) { ul.insertAdjacentHTML('beforeend', `<li>${i}</li>`); }写法二:createElement + append(节点方式,适合需要引用或事件绑定)
let li = document.createElement('li'); li.textContent = i; // 文本安全 ul.append(li); // 追加到列表末尾写法三:DocumentFragment 批量插入(减少重排,适合大批量)
article.md 的 DocumentFragment 一节演示了把多个<li>先装进 fragment、再一次插入:
let fragment = new DocumentFragment(); for (let i = 1; i <= 3; i++) { let li = document.createElement('li'); li.append(i); fragment.append(li); } ul.append(fragment); // fragment 会"融入",其子节点直接成为 ul 的子节点更简化的等价写法是返回节点数组后用展开运算符一次插入:
ul.append(...getListContent());小结
- 在两个既有兄弟节点之间插入一段 HTML 字符串时,
one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>')是官方推荐方案:精准、不重写父元素、字符串按 HTML 解析; insertAdjacentHTML的四个位置关键字beforebegin/afterbegin/beforeend/afterend覆盖了"元素外前后"与"元素内首尾"全部插入点;append/prepend/before/after适合插入节点或需要转义的纯文本,insertAdjacentHTML适合插入结构化 HTML,二者互补而非替代;- 涉及用户输入时,优先
textContent或createElement构造节点,避免直接插入 HTML 带来的 XSS 风险; - 大批量插入节点时可借助
DocumentFragment或数组展开一次性完成。
如需继续深入,可在仓库中对照阅读 章节全文 article.md、任务解答 solution.md,以及安全文本处理的 Create a list 任务,形成完整的"创建 → 插入 → 移除 → 克隆"DOM 操作知识链。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
5分钟构建你的AI浏览器助手:让Stagehand解放你的网页自动化工作
5分钟构建你的AI浏览器助手:让Stagehand解放你的网页自动化工作 你是否曾经花费数小时编写复杂的浏览器自动化脚本,只为完成一个简单的网页操作?或者面对动
文档/教程前端Modern JavaScript Tutorial 闭包章节精讲:函数声明在 if 块中的作用域陷阱(Function in if)
Modern JavaScript Tutorial 闭包章节精讲:函数声明在 if 块中的作用域陷阱(Function in if) 本篇技术指南以 Mode
文档/教程前端Modern JavaScript Tutorial 数组实战:用 sumInput() 实现 prompt 循环输入求和
Modern JavaScript Tutorial 数组实战:用 sumInput 实现 prompt 循环输入求和 导读 sumInput 是 Modern
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考