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
中的某個位置。例如,插入由 document.body
參照的 <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 節點或文字片段。
但是,如果我們想要像 elem.innerHTML
一樣「作為 html」插入 HTML 字串,讓所有標籤和內容都能正常運作,該怎麼辦?
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
主要是因為它有一些概念,例如 template 元素,我們會在稍後討論。
舊式的插入/移除方法
還有「舊式」的 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)
-
將
node
插入parentElem
中的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…)