DOM 修改是创建“实时”页面的关键。
这里我们将看到如何“动态”创建新元素并修改现有页面内容。
示例:显示消息
让我们通过一个示例进行演示。我们将在页面上添加一条比 alert
更好看的提示消息。
它将如下所示
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>
这是 HTML 示例。现在让我们用 JavaScript 创建相同的 div
(假设样式已在 HTML/CSS 中)。
创建元素
要创建 DOM 节点,有两种方法
document.createElement(tag)
-
使用给定的标签创建一个新的元素节点
let div = document.createElement('div');
document.createTextNode(text)
-
使用给定的文本创建一个新的文本节点
let textNode = document.createTextNode('Here I am');
大多数情况下,我们需要创建元素节点,例如消息的 div
。
创建消息
创建消息 div 需要 3 个步骤
// 1. Create <div> element
let div = document.createElement('div');
// 2. Set its class to "alert"
div.className = "alert";
// 3. Fill it with the content
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
我们已经创建了元素。但现在它只在一个名为 div
的变量中,尚未在页面中。因此我们看不到它。
插入方法
要使 div
显示,我们需要将其插入到 document
中的某个位置。例如,插入到 <body>
元素中,该元素由 document.body
引用。
为此,有一个特殊的方法 append
:document.body.append(div)
。
以下是完整代码
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
</script>
在这里,我们在 document.body
上调用了 append
,但我们可以对任何其他元素调用 append
方法,以将另一个元素放入其中。例如,我们可以通过调用 div.append(anotherElement)
将某些内容附加到 <div>
。
以下是更多插入方法,它们指定了不同的插入位置
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
。
这些方法的参数是待插入的 DOM 节点的任意列表,或文本字符串(自动成为文本节点)。
让我们看看它们的作用。
以下是如何使用这些方法向列表和列表前后的文本添加项的示例
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // insert string "before" before <ol>
ol.after('after'); // insert string "after" after <ol>
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // insert liFirst at the beginning of <ol>
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // insert liLast at the end of <ol>
</script>
以下是对这些方法的作用的直观描述
因此,最终列表将是
before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after
如上所述,这些方法可以在一次调用中插入多个节点和文本片段。
例如,此处插入了一个字符串和一个元素
<div id="div"></div>
<script>
div.before('<p>Hello</p>', document.createElement('hr'));
</script>
请注意:文本以“文本”形式插入,而不是以“HTML”形式插入,并正确转义了诸如 <
、>
等字符。
因此,最终 HTML 为
<p>Hello</p>
<hr>
<div id="div"></div>
换句话说,字符串以安全的方式插入,就像 elem.textContent
所做的那样。
因此,这些方法只能用于插入 DOM 节点或文本片段。
但是,如果我们想以“HTML”形式插入一个 HTML 字符串,并让所有标签和内容正常工作,就像 elem.innerHTML
所做的那样,该怎么办?
insertAdjacentHTML/Text/Element
为此,我们可以使用另一种非常通用的方法:elem.insertAdjacentHTML(where, html)
。
第一个参数是一个代码字,指定相对于 elem
的插入位置。必须是以下之一
"beforebegin"
– 在elem
正前方插入html
,"afterbegin"
– 在elem
内,从开头插入html
,"beforeend"
– 在elem
内,从结尾插入html
,"afterend"
– 在elem
正后方插入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>
这就是我们如何将任意 HTML 附加到页面。
以下是插入变体的图片
我们可以轻松地注意到它与上一张图片之间的相似之处。插入点实际上是相同的,但此方法插入 HTML。
该方法有两个兄弟
elem.insertAdjacentText(where, text)
– 语法相同,但插入的text
字符串以“文本”形式插入,而不是 HTML,elem.insertAdjacentElement(where, elem)
– 语法相同,但插入一个元素。
它们主要存在于使语法“统一”。在实践中,大多数时候只使用 insertAdjacentHTML
。因为对于元素和文本,我们有方法 append/prepend/before/after
– 它们更短,可以插入节点/文本片段。
因此,以下是一种显示消息的替代变体
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
document.body.insertAdjacentHTML("afterbegin", `<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>`);
</script>
节点删除
要删除节点,有一个方法 node.remove()
。
让我们让我们的消息在一秒后消失
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
document.body.append(div);
setTimeout(() => div.remove(), 1000);
</script>
请注意:如果我们想将元素移动到另一个位置,则无需将其从旧位置中删除。
所有插入方法都会自动从旧位置中删除节点。
例如,让我们交换元素
<div id="first">First</div>
<div id="second">Second</div>
<script>
// no need to call remove
second.after(first); // take #second and after it insert #first
</script>
克隆节点:cloneNode
如何插入一条类似的消息?
我们可以创建一个函数并把代码放在那里。但另一种方法是克隆现有的 div
并修改其中的文本(如果需要)。
有时当我们有一个大元素时,这可能更快、更简单。
- 调用
elem.cloneNode(true)
会创建一个元素的“深度”克隆 – 包含所有属性和子元素。如果我们调用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); // clone the message
div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone
div.after(div2); // show the clone after the existing div
</script>
DocumentFragment
DocumentFragment
是一个特殊的 DOM 节点,用作传递节点列表的包装器。
我们可以向其中追加其他节点,但当我们将其插入某处时,则会插入其内容。
例如,下面的 getListContent
生成一个带有 <li>
项的片段,稍后插入到 <ul>
中
<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.append(getListContent()); // (*)
</script>
请注意,在最后一行 (*)
中,我们追加了 DocumentFragment
,但它“融入了”,因此结果结构将是
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
DocumentFragment
很少被显式使用。为什么追加到一种特殊类型的节点,如果我们可以返回一个节点数组?重写的示例
<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()); // append + "..." operator = friends!
</script>
我们主要提到 DocumentFragment
,因为有一些概念基于它,例如 模板 元素,我们将在后面详细介绍。
老派插入/删除方法
还有一些“老派”的 DOM 操作方法,出于历史原因而存在。
这些方法来自非常古老的时代。如今,没有理由使用它们,因为现代方法(如 append
、prepend
、before
、after
、remove
、replaceWith
)更灵活。
我们在这里列出这些方法的唯一原因是,你可以在许多旧脚本中找到它们
parentElem.appendChild(node)
-
将
node
追加为parentElem
的最后一个子元素。以下示例在
<ol>
的末尾添加了一个新的<li>
<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.appendChild(newLi); </script>
parentElem.insertBefore(node, nextSibling)
-
在
parentElem
中将node
插入到nextSibling
之前。以下代码在第二个
<li>
之前插入了一个新的列表项<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.insertBefore(newLi, list.children[1]); </script>
要将
newLi
作为第一个元素插入,我们可以这样做list.insertBefore(newLi, list.firstChild);
parentElem.replaceChild(node, oldChild)
-
用
node
替换parentElem
的子元素中的oldChild
。 parentElem.removeChild(node)
-
从
parentElem
中删除node
(假设node
是其子元素)。以下示例从
<ol>
中删除第一个<li>
<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let li = list.firstElementChild; list.removeChild(li); </script>
所有这些方法都返回已插入/已删除的节点。换句话说,parentElem.appendChild(node)
返回 node
。但通常不使用返回值,我们只运行该方法。
关于“document.write”的一句话
还有一种更古老的方法可以将某些内容添加到网页中:document.write
。
语法
<p>Somewhere in the page...</p>
<script>
document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>
调用 document.write(html)
会将 html
写入页面“此处此刻”。html
字符串可以动态生成,因此非常灵活。我们可以使用 JavaScript 创建一个完整的网页并将其写入。
该方法来自没有 DOM、没有标准的时代……非常久远。它仍然存在,因为有脚本在使用它。
在现代脚本中,我们很少看到它,因为有以下重要的限制
调用 document.write
仅在页面加载时有效。
如果我们随后调用它,则现有文档内容将被擦除。
例如
<p>After one second the contents of this page will be replaced...</p>
<script>
// document.write after 1 second
// that's after the page loaded, so it erases the existing content
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
因此,与我们上面介绍的其他 DOM 方法不同,它在“加载后”阶段有点不可用。
这是缺点。
也有优点。从技术上讲,当浏览器正在读取(“解析”)传入的 HTML 时调用 document.write
,并且它写入了一些内容,浏览器会像最初在 HTML 文本中一样使用它。
因此,它的工作速度非常快,因为不涉及DOM 修改。它直接写入页面文本,而 DOM 尚未构建。
因此,如果我们需要动态地向 HTML 中添加大量文本,并且我们处于页面加载阶段,并且速度很重要,它可能会有所帮助。但在实践中,这些要求很少同时出现。而且通常我们只能在脚本中看到此方法,仅仅是因为它们很旧。
总结
-
创建新节点的方法
document.createElement(tag)
– 创建具有给定标记的元素,document.createTextNode(value)
– 创建文本节点(很少使用),elem.cloneNode(deep)
– 克隆元素,如果deep==true
,则克隆所有后代。
-
插入和删除
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
。
文本字符串以“文本”形式插入。
-
还有一些“老派”方法
parent.appendChild(node)
parent.insertBefore(node, nextSibling)
parent.removeChild(node)
parent.replaceChild(newElem, node)
所有这些方法都返回
node
。 -
给定
html
中的某些 HTML,elem.insertAdjacentHTML(where, html)
会根据where
的值插入它"beforebegin"
– 在elem
之前插入html
,"afterbegin"
– 在elem
内,从开头插入html
,"beforeend"
– 在elem
内,从结尾插入html
,"afterend"
– 在elem
之后插入html
。
还有类似的方法,
elem.insertAdjacentText
和elem.insertAdjacentElement
,它们插入文本字符串和元素,但它们很少使用。 -
要在页面加载完成之前向页面追加 HTML
document.write(html)
页面加载后,此类调用会擦除文档。主要出现在旧脚本中。
评论
<code>
标记,对于多行 - 将其包装在<pre>
标记中,对于 10 行以上 - 使用沙盒 (plnkr,jsbin,codepen…)