JavaScript 修改 DOM
修改 DOM 是创建「实时」页面的关键。常用的修改 DOM 方法如下
创建新节点的方法
document.createElement(tag)用给定的标签创建一个元素节点document.createTextNode(value)创建一个文本节点(很少使用)elem.cloneNode(deep)克隆元素,如果deep==true则与其后代一起克隆
Tip
如果需要创建一个注释节点可以使用方法 document.createComment()
插入和移除节点的方法
node.append(...nodes or strings)在node(内部)末尾插入,node.prepend(...nodes or strings)在node(内部)开头插入,node.before(...nodes or strings)在node之前插入,node.after(...nodes or strings)在node之后插入,node.replaceWith(...nodes or strings)替换node。node.remove()移除node。
Warning
使用这些方法添加 HTML 片段会被作为纯文本插入。
「旧式」方法
调用这些方法将节点(HTML 片段)插入到指定位置,而且一般返回新增的 node。
parent.appendChild(node)parent.insertBefore(node, nextSibling)当第二个参数传递null相当于parent.appendChild(node)parent.removeChild(node)parent.replaceChild(newElem, node)返回被替换的节点node以备还需要使用被删除的节点
专门用于插入 html 代码的方法 elem.insertAdjacentHTML(where, html) 会根据 where 的值来插入它:
"beforebegin"— 将html插入到elem前面,"afterbegin"— 将html插入到elem的开头,"beforeend"— 将html插入到elem的末尾,"afterend"— 将html插入到elem后面。
Tip
类似的方法,elem.insertAdjacentText 和 elem.insertAdjacentElement 它们会插入文本字符串和元素节点,但很少使用。
要在页面加载完成之前将 HTML 附加到页面(多见于旧脚本) document.write(html),⚠️ 如果在页面加载完成后调用该方法,将会擦除文档。
创建元素
有两种方法创建新的 DOM 节点
document.createElement(tag)用给定的标签创建一个新元素节点(element node)document.createTextNode(text)用给定的文本创建一个文本节点
然后为了让新增的节点显示出来,可以使用多种方法将新增的节点插入到 DOM 树结构中

node.append(otherNodes or strings)在node内部的末尾插入其他节点或字符串node.prepend(otherNodes or strings)在node内部的开头插入其他节点或字符串node.before(otherNodes or strings)在node前面插入其他节点或字符串node.after(otherNodes or strings)在node后面插入其他节点或字符串node.replaceWith(otherNodes or strings)将node替换为给定的其他节点或字符串
Tip
这些方法可以在单个调用中插入多个节点列表和文本片段,如果插入的是带 HTML 代码片段,它们都会被作为纯文本(字符串)形式插入,就相当于 elem.textContent 所做的一样
<div id="div"></div>
<script>
div.before('<p>Hello</p>', document.createElement('hr'));
</script>

Tip
如果希望插入 HTML 代码并正确解析显示出来,可以先使用属性 innerHTML 将需要插入的 HTML 片段包含到新建的节点里。
<div id="elem1"></div>
<div id="elem2"></div>
<div id="elem3"></div>
<script>
let text = '<b>text</b>';
elem1.append(document.createTextNode(text));
elem2.innerHTML = text;
elem3.textContent = text; // 相当于第一种方法
</script>

Tip
节点属性 className 获取类属性值,也可以通过赋值来设置相应元素的类属性。
也可以使用另一个非常通用的方法 elem.insertAdjacentHTML(where, html)
Announce
参数说明:
- 第一个参数
where指定代码相对于elem插入位置,必须为以下之一:"beforebegin"将html插入到elem前"afterbegin"将html插入到elem开头"beforeend"将html插入到elem末尾"afterend"将html插入到elem后

insert html snippet - 第二个参数
html是 HTML 字符串,该字符串会被作为 HTML 插入。
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>
🔨 执行结果
<p>Hello</p>
<div id="div"></div>
<p>Bye</p>
Tip
还有两个类似的方法
elem.insertAdjacentText(where, text)语法一样,但是将text字符串作为纯文本插入而不是作为 HTMLelem.insertAdjacentElement(where, elem)语法一样,但是插入的是一个元素节点
但一般很少使用这两种方法,如果插入的是 HTML 代码片段就使用方法 inserAdjacentHTML,如果插入的是元素节点(对象)或文本就使用方法 append/prepend/before/after
Tip
插入节点还有一些老式的方法 (不推荐使用,而应该使用 append,prepend,before,after,remove,replaceWith 这些更加灵活的现代方法),可能会在许多就脚本中遇到它们。
parentElem.appendChild(node)将node作为最后一个子元素附加到parentElem内parentElem.insertBefore(node, nextSibling)在parentElem的nextSibling前插入nodeparentElem.replaceChild(node, oldChild)将parentElem的后代中的oldChild替换为nodeparentElem.removeChild(node)从parentElem中删除node(假设node为parentElem的后代)
所有这些方法都会返回插入/删除的节点,如 parentElem.appendChild(node) 返回 node,但是通常我们不会使用返回值,我们只是使用对应的方法。
Warning
另一个「古老」的方法 document.write(html) 可以将 HTML 代码写入页面,这个方法来自于没有 DOM,没有标准的「上古时期」,但因为还有脚本在使用它,所以这个方法依被保留了下来。它有一个巨大的缺陷,即只在页面加载时工作,会「就地立马」将 HTML 写入页面,如果在稍后调用则原有的整个文档内容将被擦除,因此在某种程度上讲,它在「加载完成」阶段是不可用的,这与其他现代的操作 DOM 方法不同。
<p>After one second the contents of this page will be replaced...</p>
<script>
// 1 秒后调用 document.write
// 这时页面已经加载完成,所以它会擦除现有内容,最终页面只有 ...By this 字符串
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
但由于它是在浏览器解析 HTML 时调用的,因为它 不涉及 DOM 修改(此时 DOM 尚未构建),它运行起来出奇的快,这是它的一个好处。因此如果我们需要向 HTML 动态地添加大量文本,并且我们正处于页面加载阶段,并且速度很重要,那么它可能会有帮助。但实际上这些要求很少同时出现。
节点移除
可以使用方法 node.remove() 移除一个节点
Tip
如果我们要将一个元素 移动 到另一个地方,该节点会先从自动从旧位置删除(因此无需调用方法 remove()),再插入到相应的新位置。
// 进行元素交换
<div id="first">First</div>
<div id="second">Second</div>
<script>
// 无需调用 remove
second.after(first); // 获取 #second 并在其后面插入 #first 后
</script>
克隆节点
方法 elem.cloneNode(true) 创建元素 elem 的一个「深」克隆,即拷贝该元素节点的所有特性(attribute)和子元素;如果调用 elem.cloneNode(false)(默认)克隆的节点就不包括子元素。
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert" id="div">
<strong>Hi there!</strong> You've read an important message.
</div>
<script>
let div2 = div.cloneNode(true); // 克隆消息
div2.querySelector('strong').innerHTML = 'Bye there!'; // 修改克隆
div.after(div2); // 在已有的 div 后显示克隆
</script>

文本节点合并
如果通过 DOM 操作为元素添加文本(节点)可能造成视觉上连续的字符串实际是由多个文本节点构成的,这会对后续的 DOM 操作造成不便,可以使用 parent.normalize() 将给定的元素节点内的文本节点合并。
let div = document.querySelector('div');
let text1 = document.createTextNode('text one');
let text2 = document.createTextNode('text two');
div.append(text1);
div.append(text2);
console.log(div.childNodes.length); // 2
div.normalize();
console.log(div.childNodes.length); // 1
文本节点分割
使用 parent.splitText(n) 以传递的参数索引,在文本子节点的相应字符的位置分割字符串,返回分割后产生的新的文本子节点(后一半段字符串)。
let div = document.querySelector('div');
let text = document.createTextNode('longText');
div.append(text);
let newNode = div.firstChild.splitText(4);
console.log(div.firstChild.nodeValue); // long
console.log(newNode.nodeValue); // Text
DocumentFragment
DocumentFragment 是一个特殊的 DOM 节点,作为来传递节点列表的包装器 wrapper,即我们可以将多个节点先添加 append 到该容器中,并将容器再添加到目标节点内,则包装器中的所有节点就会作为内容插入到目标节点内。
<ul id="ul"></ul>
<script>
function getListContent() {
// 创建节点列表容器
let fragment = new DocumentFragment();
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
fragment.append(li); // 收集节点
}
return fragment;
}
// 添加节点到 ul 元素内
ul.append(getListContent());
</script>

但DocumentFragment 很少被显式使用,我们一般通过数组收集节点,再结合 spread 语法将数组「分解」为单个元素添加 append 到目标节点内
<!-- 功能相同 -->
<ul id="ul"></ul>
<script>
function getListContent() {
let result = [];
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
result.push(li);
}
return result;
}
ul.append(...getListContent()); // 使用 spread 操作符
</script>