DOM 修改是創(chuàng)建“實(shí)時(shí)”頁(yè)面的關(guān)鍵。
在這里,我們將會(huì)看到如何“即時(shí)”創(chuàng)建新元素并修改現(xiàn)有頁(yè)面內(nèi)容。
讓我們使用一個(gè)示例進(jìn)行演示。我們將在頁(yè)面上添加一條比 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>
這是一個(gè) HTML 示例。現(xiàn)在,讓我們使用 JavaScript 創(chuàng)建一個(gè)相同的 div
(假設(shè)樣式已經(jīng)在 HTML/CSS 文件中)。
要?jiǎng)?chuàng)建 DOM 節(jié)點(diǎn),這里有兩種方法:
?document.createElement(tag)
?
用給定的標(biāo)簽創(chuàng)建一個(gè)新 元素節(jié)點(diǎn)(element node):
let div = document.createElement('div');
?document.createTextNode(text)
?
用給定的文本創(chuàng)建一個(gè) 文本節(jié)點(diǎn):
let textNode = document.createTextNode('Here I am');
大多數(shù)情況下,我們需要為此消息創(chuàng)建像 div
這樣的元素節(jié)點(diǎn)。
創(chuàng)建一個(gè)消息 div
分為 3 個(gè)步驟:
// 1. 創(chuàng)建 <div> 元素
let div = document.createElement('div');
// 2. 將元素的類設(shè)置為 "alert"
div.className = "alert";
// 3. 填充消息內(nèi)容
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
我們已經(jīng)創(chuàng)建了該元素。但到目前為止,它還只是在一個(gè)名為 div
的變量中,尚未在頁(yè)面中。所以我們無(wú)法在頁(yè)面上看到它。
為了讓 div
顯示出來(lái),我們需要將其插入到 document
中的某處。例如,通過(guò) document.body
將其插入到 <body>
元素里。
對(duì)此有一個(gè)特殊的方法 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>
在這個(gè)例子中,我們對(duì) document.body
調(diào)用了 append
方法。不過(guò)我們可以在其他任何元素上調(diào)用 append
方法,以將另外一個(gè)元素放入到里面。例如,通過(guò)調(diào)用 div.append(anotherElement)
,我們便可以在 <div>
末尾添加一些內(nèi)容。
這里是更多的元素插入方法,指明了不同的插入位置:
node.append(...nodes or strings)
? —— 在 ?node
? 末尾 插入節(jié)點(diǎn)或字符串,node.prepend(...nodes or strings)
? —— 在 ?node
? 開(kāi)頭 插入節(jié)點(diǎn)或字符串,node.before(...nodes or strings)
? —— 在 ?node
? 前面 插入節(jié)點(diǎn)或字符串,node.after(...nodes or strings)
? —— 在 ?node
? 后面 插入節(jié)點(diǎn)或字符串,node.replaceWith(...nodes or strings)
? —— 將 ?node
? 替換為給定的節(jié)點(diǎn)或字符串。這些方法的參數(shù)可以是一個(gè)要插入的任意的 DOM 節(jié)點(diǎn)列表,或者文本字符串(會(huì)被自動(dòng)轉(zhuǎn)換成文本節(jié)點(diǎn))。
讓我們?cè)趯?shí)際應(yīng)用中看一看。
下面是使用這些方法將列表項(xiàng)添加到列表中,以及將文本添加到列表前面和后面的示例:
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // 將字符串 "before" 插入到 <ol> 前面
ol.after('after'); // 將字符串 "after" 插入到 <ol> 后面
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // 將 liFirst 插入到 <ol> 的最開(kāi)始
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // 將 liLast 插入到 <ol> 的最末尾
</script>
這張圖片直觀地顯示了這些方法所做的工作:
因此,最終列表將為:
before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after
如上所述,這些方法可以在單個(gè)調(diào)用中插入多個(gè)節(jié)點(diǎn)列表和文本片段。
例如,在這里插入了一個(gè)字符串和一個(gè)元素:
<div id="div"></div>
<script>
div.before('<p>Hello</p>', document.createElement('hr'));
</script>
請(qǐng)注意:這里的文字都被“作為文本”插入,而不是“作為 HTML 代碼”。因此像 <
、>
這樣的符號(hào)都會(huì)被作轉(zhuǎn)義處理來(lái)保證正確顯示。
所以,最終的 HTML 為:
<p>Hello</p>
<hr>
<div id="div"></div>
換句話說(shuō),字符串被以一種安全的方式插入到頁(yè)面中,就像 elem.textContent
所做的一樣。
所以,這些方法只能用來(lái)插入 DOM 節(jié)點(diǎn)或文本片段。
但如果我們想要將內(nèi)容“作為 HTML 代碼插入”,讓內(nèi)容中的所有標(biāo)簽和其他東西都像使用 elem.innerHTML
所表現(xiàn)的效果一樣,那應(yīng)該怎么辦呢?
為此,我們可以使用另一個(gè)非常通用的方法:elem.insertAdjacentHTML(where, html)
。
該方法的第一個(gè)參數(shù)是代碼字(code word),指定相對(duì)于 elem
的插入位置。必須為以下之一:
"beforebegin"
? —— 將 ?html
? 插入到 ?elem
? 之前,"afterbegin"
? —— 將 ?html
? 插入到 ?elem
? 開(kāi)頭,"beforeend"
? —— 將 ?html
? 插入到 ?elem
? 末尾,"afterend"
? —— 將 ?html
? 插入到 ?elem
? 之后。第二個(gè)參數(shù)是 HTML 字符串,該字符串會(huì)被“作為 HTML” 插入。
例如:
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>
……將導(dǎo)致:
<p>Hello</p>
<div id="div"></div>
<p>Bye</p>
這就是我們可以在頁(yè)面上附加任意 HTML 的方式。
這是插入變體的示意圖:
我們很容易就會(huì)注意到這張圖片和上一張圖片的相似之處。插入點(diǎn)實(shí)際上是相同的,但此方法插入的是 HTML。
這個(gè)方法有兩個(gè)兄弟:
elem.insertAdjacentText(where, text)
? —— 語(yǔ)法一樣,但是將 ?text
? 字符串“作為文本”插入而不是作為 HTML,elem.insertAdjacentElement(where, elem)
? —— 語(yǔ)法一樣,但是插入的是一個(gè)元素。它們的存在主要是為了使語(yǔ)法“統(tǒng)一”。實(shí)際上,大多數(shù)時(shí)候只使用 insertAdjacentHTML
。因?yàn)閷?duì)于元素和文本,我們有 append/prepend/before/after
方法 —— 它們也可以用于插入節(jié)點(diǎn)/文本片段,但寫起來(lái)更短。
所以,下面是顯示一條消息的另一種變體:
<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>
想要移除一個(gè)節(jié)點(diǎn),可以使用 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>
請(qǐng)注意:如果我們要將一個(gè)元素 移動(dòng) 到另一個(gè)地方,則無(wú)需將其從原來(lái)的位置中刪除。
所有插入方法都會(huì)自動(dòng)從舊位置刪除該節(jié)點(diǎn)。
例如,讓我們進(jìn)行元素交換:
<div id="first">First</div>
<div id="second">Second</div>
<script>
// 無(wú)需調(diào)用 remove
second.after(first); // 獲取 #second,并在其后面插入 #first
</script>
如何再插入一條類似的消息?
我們可以創(chuàng)建一個(gè)函數(shù),并將代碼放在其中。但是另一種方法是 克隆 現(xiàn)有的 div
,并修改其中的文本(如果需要)。
當(dāng)我們有一個(gè)很大的元素時(shí),克隆的方式可能更快更簡(jiǎn)單。
調(diào)用 elem.cloneNode(true)
來(lái)創(chuàng)建元素的一個(gè)“深”克隆 —— 具有所有特性(attribute)和子元素。如果我們調(diào)用 elem.cloneNode(false)
,那克隆就不包括子元素。
一個(gè)拷貝消息的示例:
<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>
DocumentFragment
是一個(gè)特殊的 DOM 節(jié)點(diǎn),用作來(lái)傳遞節(jié)點(diǎn)列表的包裝器(wrapper)。
我們可以向其附加其他節(jié)點(diǎn),但是當(dāng)我們將其插入某個(gè)位置時(shí),則會(huì)插入其內(nèi)容。
例如,下面這段代碼中的 getListContent
會(huì)生成帶有 <li>
列表項(xiàng)的片段,然后將其插入到 <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>
請(qǐng)注意,在最后一行 (*)
我們附加了 DocumentFragment
,但是它和 ul
“融為一體(blends in)”了,所以最終的文檔結(jié)構(gòu)應(yīng)該是:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
DocumentFragment
很少被顯式使用。如果可以改為返回一個(gè)節(jié)點(diǎn)數(shù)組,那為什么還要附加到特殊類型的節(jié)點(diǎn)上呢?重寫示例:
<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
,主要是因?yàn)樗厦嬗幸恍└拍睿?nbsp;template 元素,我們將在以后討論。
老式用法
這些內(nèi)容有助于理解舊腳本,但不適合用于新代碼的開(kāi)發(fā)中。
由于歷史原因,還存在“老式”的 DOM 操作方法。
這些方法來(lái)自真正的遠(yuǎn)古時(shí)代。如今,沒(méi)有理由再使用它們了,因?yàn)橹T如 append
,prepend
,before
,after
,remove
,replaceWith
這些現(xiàn)代方法更加靈活。
我們?cè)谶@兒列出這些方法的唯一原因是,你可能會(huì)在許多腳本中遇到它們。
?parentElem.appendChild(node)
?
將 node
附加為 parentElem
的最后一個(gè)子元素。
下面這個(gè)示例在 <ol>
的末尾添加了一個(gè)新的 <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
的 nextSibling
前插入 node
。
下面這段代碼在第二個(gè) <li>
前插入了一個(gè)新的列表項(xiàng):
<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
插入為第一個(gè)元素,我們可以這樣做:
list.insertBefore(newLi, list.firstChild);
?parentElem.replaceChild(node, oldChild)
?
將 parentElem
的后代中的 oldChild
替換為 node
。
?parentElem.removeChild(node)
?
從 parentElem
中刪除 node
(假設(shè) node
為 parentElem
的后代)。
下面這個(gè)示例從 <ol>
中刪除了 <li>
:
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
let li = list.firstElementChild;
list.removeChild(li);
</script>
所有這些方法都會(huì)返回插入/刪除的節(jié)點(diǎn)。換句話說(shuō),parentElem.appendChild(node)
返回 node
。但是通常我們不會(huì)使用返回值,我們只是使用對(duì)應(yīng)的方法。
還有一個(gè)非常古老的向網(wǎng)頁(yè)添加內(nèi)容的方法:document.write
。
語(yǔ)法如下:
<p>Somewhere in the page...</p>
<script>
document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>
調(diào)用 document.write(html)
意味著將 html
“就地馬上”寫入頁(yè)面。html
字符串可以是動(dòng)態(tài)生成的,所以它很靈活。我們可以使用 JavaScript 創(chuàng)建一個(gè)完整的頁(yè)面并對(duì)其進(jìn)行寫入。
這個(gè)方法來(lái)自于沒(méi)有 DOM,沒(méi)有標(biāo)準(zhǔn)的上古時(shí)期……。但這個(gè)方法依被保留了下來(lái),因?yàn)檫€有腳本在使用它。
由于以下重要的限制,在現(xiàn)代腳本中我們很少看到它:
document.write
調(diào)用只在頁(yè)面加載時(shí)工作。
如果我們稍后調(diào)用它,則現(xiàn)有文檔內(nèi)容將被擦除。
例如:
<p>After one second the contents of this page will be replaced...</p>
<script>
// 1 秒后調(diào)用 document.write
// 這時(shí)頁(yè)面已經(jīng)加載完成,所以它會(huì)擦除現(xiàn)有內(nèi)容
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
因此,在某種程度上講,它在“加載完成”階段是不可用的,這與我們上面介紹的其他 DOM 方法不同。
這是它的缺陷。
還有一個(gè)好處。從技術(shù)上講,當(dāng)在瀏覽器正在讀?。ā敖馕觥保﹤魅氲?HTML 時(shí)調(diào)用 document.write
方法來(lái)寫入一些東西,瀏覽器會(huì)像它本來(lái)就在 HTML 文本中那樣使用它。
所以它運(yùn)行起來(lái)出奇的快,因?yàn)樗?nbsp;不涉及 DOM 修改。它直接寫入到頁(yè)面文本中,而此時(shí) DOM 尚未構(gòu)建。
因此,如果我們需要向 HTML 動(dòng)態(tài)地添加大量文本,并且我們正處于頁(yè)面加載階段,并且速度很重要,那么它可能會(huì)有幫助。但實(shí)際上,這些要求很少同時(shí)出現(xiàn)。我們可以在腳本中看到此方法,通常是因?yàn)檫@些腳本很舊。
document.createElement(tag)
? —— 用給定的標(biāo)簽創(chuàng)建一個(gè)元素節(jié)點(diǎn),document.createTextNode(value)
? —— 創(chuàng)建一個(gè)文本節(jié)點(diǎn)(很少使用),elem.cloneNode(deep)
? —— 克隆元素,如果 ?deep==true
? 則與其后代一起克隆。node.append(...nodes or strings)
? —— 在 ?node
? 末尾插入,node.prepend(...nodes or strings)
? —— 在 ?node
? 開(kāi)頭插入,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)
? 會(huì)根據(jù) ?where
? 的值來(lái)插入它:"beforebegin"
? —— 將 ?html
? 插入到 ?elem
? 前面,"afterbegin"
? —— 將 ?html
? 插入到 ?elem
? 的開(kāi)頭,"beforeend"
? —— 將 ?html
? 插入到 ?elem
? 的末尾,"afterend"
? —— 將 ?html
? 插入到 ?elem
? 后面。另外,還有類似的方法,elem.insertAdjacentText
和 elem.insertAdjacentElement
,它們會(huì)插入文本字符串和元素,但很少使用。
document.write(html)
?頁(yè)面加載完成后,這樣的調(diào)用將會(huì)擦除文檔。多見(jiàn)于舊腳本。
我們有一個(gè)空的 DOM 元素 elem
和一個(gè)字符串 text
。
下面這 3 個(gè)命令中的哪些命令會(huì)執(zhí)行完全相同的操作?
elem.append(document.createTextNode(text))
?elem.innerHTML = text
?elem.textContent = text
?回答:1 和 3。
這兩個(gè)命令都會(huì)將 text
“作為文本”添加到 elem
中。
這是一個(gè)例子:
<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>
創(chuàng)建一個(gè)函數(shù) ?clear(elem)
? 用來(lái)移除元素里的內(nèi)容。
<ol id="elem">
<li>Hello</li>
<li>World</li>
</ol>
<script>
function clear(elem) { /* 你的代碼 */ }
clear(elem); // 清除列表
</script>
首先,讓我們看看 錯(cuò)誤 的做法:
function clear(elem) {
for (let i=0; i < elem.childNodes.length; i++) {
elem.childNodes[i].remove();
}
}
這是行不通的,因?yàn)檎{(diào)用 remove()
會(huì)從首端開(kāi)始移除 elem.childNodes
集合中的元素,因此,元素每次都從索引 0
開(kāi)始。但是 i
在增加,所以元素就被跳過(guò)了。
用 for..of
循環(huán)的結(jié)果也跟上面一樣。
正確的做法是:
function clear(elem) {
while (elem.firstChild) {
elem.firstChild.remove();
}
}
還有一種更簡(jiǎn)單的方法,也可以達(dá)到我們所要的效果:
function clear(elem) {
elem.innerHTML = '';
}
在下面這個(gè)示例中,我們調(diào)用 table.remove()
從文檔中刪除表格。
但如果運(yùn)行它,你就會(huì)看到文本 "aaa"
并沒(méi)有被刪除。
這是為什么?
<table id="table">
aaa
<tr>
<td>Test</td>
</tr>
</table>
<script>
alert(table); // 表格,就是它應(yīng)有的樣子
table.remove();
// 為什么 "aaa" 還存在于文檔中?
</script>
這個(gè)題目中的 HTML 是錯(cuò)的。這就是造成怪異現(xiàn)象的原因。
瀏覽器必須自動(dòng)修復(fù)它。但 <table>
內(nèi)可能會(huì)沒(méi)有文本:根據(jù)規(guī)范,只允許特定于表格的標(biāo)簽。所以瀏覽器將 "aaa"
展示在了 <table>
前面。
當(dāng)我們刪除表格后,文本 "aaa"
仍然存在的原因就很明顯了吧。
通過(guò)使用瀏覽器開(kāi)發(fā)者工具查看 DOM,就可以輕松地回答這個(gè)問(wèn)題。從瀏覽器開(kāi)發(fā)者工具中我們可以看到,"aaa"
在 <table>
前面。
HTML 標(biāo)準(zhǔn)規(guī)范詳細(xì)描述了如何處理錯(cuò)誤的 HTML,并且瀏覽器的這種行為是正確的。
編寫一個(gè)接口,根據(jù)用戶輸入創(chuàng)建一個(gè)列表(list)。
對(duì)于每個(gè)列表項(xiàng):
prompt
? 向用戶詢問(wèn)列表項(xiàng)的內(nèi)容。<li>
?,并添加到 ?<ul>
?。Esc
? 鍵,或輸入一個(gè)空內(nèi)容)。所有元素應(yīng)該都是動(dòng)態(tài)創(chuàng)建的。
如果用戶輸入了 HTML 標(biāo)簽,那么這些內(nèi)容應(yīng)該被視為文本進(jìn)行后續(xù)處理。
請(qǐng)注意使用 textContent
對(duì) <li>
的內(nèi)容進(jìn)行賦值的用法。
編寫一個(gè)函數(shù) createTree
,從嵌套對(duì)象創(chuàng)建一個(gè)嵌套的 ul/li
列表(list)。
例如:
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"apple tree": {},
"magnolia": {}
}
}
};
語(yǔ)法:
let container = document.getElementById('container');
createTree(container, data); // 將樹(shù)創(chuàng)建在 container 中
結(jié)果(樹(shù))看起來(lái)像這樣:
選擇下面兩種方式中的一種,來(lái)完成這個(gè)任務(wù):
container.innerHTML
?。如果這兩種方式你都做,那就太好了。
P.S. 樹(shù)上不應(yīng)該有“多余”的元素,例如空的 <ul></ul>
葉子節(jié)點(diǎn)。
遍歷對(duì)象的最簡(jiǎn)單的方法是使用遞歸。
這里有一棵由嵌套的 ul/li
組成的樹(shù)。
編寫代碼,為每個(gè) <li>
添加其后代數(shù)量。跳過(guò)葉子節(jié)點(diǎn)(沒(méi)有子代的節(jié)點(diǎn))。
結(jié)果:
為了將文本附加到每個(gè) <li>
中,我們可以改變文本節(jié)點(diǎn)的 data
。
編寫一個(gè)函數(shù) createCalendar(elem, year, month)
。
對(duì)該函數(shù)的調(diào)用,應(yīng)該使用給定的 year/month 創(chuàng)建一個(gè)日歷,并將創(chuàng)建的日歷放入 elem
中。
創(chuàng)建的日歷應(yīng)該是一個(gè)表格(table),其中每一周用 <tr>
表示,每一天用 <td>
表示。表格頂部應(yīng)該是帶有星期名的 <th>
:第一天應(yīng)該是 Monday,依此類推,直到 Sunday。
例如,createCalendar(cal, 2012, 9)
應(yīng)該在元素 cal
中生成如下所示的日歷:
P.S. 在這個(gè)任務(wù)中,生成一個(gè)日歷就可以了,不需要有點(diǎn)擊交互的功能。
我們將表格創(chuàng)建為字符串:"<table>...</table>"
,然后將其賦值給 innerHTML
。
算法如下:
<th>
? 創(chuàng)建帶有星期名的表頭。d = new Date(year, month-1)
?。它是 ?month
? 的第一天(考慮到 JavaScript 中的月份從 ?0
? 開(kāi)始,而不是從 ?1
? 開(kāi)始)。d.getDay()
?,前面的幾個(gè)單元格是空的。讓我們用 ?<td></td>
? 填充它們。d
?:?d.setDate(d.getDate()+1)
?。如果 ?d.getMonth()
? 還沒(méi)到下一個(gè)月,那么就將新的單元格 ?<td>
? 添加到日歷中。如果那天是星期日,就添加一個(gè)新行 ?“</tr><tr>”
?。<td>
? 補(bǔ)齊。創(chuàng)建一個(gè)像這樣的彩色時(shí)鐘:
使用 HTML/CSS 進(jìn)行樣式設(shè)計(jì),JavaScript 僅用來(lái)更新元素中的時(shí)間。
首先,讓我們編寫 HTML/CSS。
時(shí)間的每個(gè)組件都有其自己的 <span>
,那將會(huì)看起來(lái)很棒:
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
另外,我們需要使用 CSS 為它們著色。
函數(shù) update
會(huì)刷新時(shí)鐘,由 setInterval
每秒調(diào)用一次:
function update() {
let clock = document.getElementById('clock');
let date = new Date(); // (*)
let hours = date.getHours();
if (hours < 10) hours = '0' + hours;
clock.children[0].innerHTML = hours;
let minutes = date.getMinutes();
if (minutes < 10) minutes = '0' + minutes;
clock.children[1].innerHTML = minutes;
let seconds = date.getSeconds();
if (seconds < 10) seconds = '0' + seconds;
clock.children[2].innerHTML = seconds;
}
在 (*)
行中,我們每次都檢查當(dāng)前時(shí)間。setInterval
調(diào)用并不可靠:它們可能會(huì)發(fā)生延遲現(xiàn)象。
時(shí)鐘管理函數(shù):
let timerId;
function clockStart() { // 運(yùn)行時(shí)鐘
if (!timerId) { // 僅當(dāng)時(shí)鐘停止時(shí) setInterval
timerId = setInterval(update, 1000);
}
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null; // (**)
}
請(qǐng)注意,update()
不僅在 clockStart()
中被調(diào)度,而且還立即在 (*)
行運(yùn)行。否則,訪問(wèn)者將不得不等到 setInterval
第一次執(zhí)行。在那之前,時(shí)鐘將是空的。
此外,在 clockStart()
中僅當(dāng)時(shí)鐘未運(yùn)行時(shí)才進(jìn)行 setInterval 也是至關(guān)重要的。否則多次點(diǎn)擊 Start 按鈕會(huì)設(shè)置多個(gè)并發(fā)的間隔。更糟糕的是 —— 我們只會(huì)保留最后一個(gè)時(shí)間間隔的 timerID
,失去對(duì)所有其他時(shí)間間隔的引用。那我們就再也無(wú)法停止時(shí)鐘了!請(qǐng)注意,當(dāng)時(shí)鐘停止時(shí),我們需要在 (**)
行這樣清除 timerID
,以便可以通過(guò)執(zhí)行 clockStart()
再次啟動(dòng)時(shí)鐘。
編寫代碼,將 <li>2</li><li>3</li>
,插入到兩個(gè) <li>
之間:
<ul id="ul">
<li id="one">1</li>
<li id="two">4</li>
</ul>
當(dāng)我們需要在某處插入 HTML 時(shí),insertAdjacentHTML
是最適合的方案。
解決方法:
one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');
下面是一個(gè)表格:
<table>
<thead>
<tr>
<th>Name</th><th>Surname</th><th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td><td>Smith</td><td>10</td>
</tr>
<tr>
<td>Pete</td><td>Brown</td><td>15</td>
</tr>
<tr>
<td>Ann</td><td>Lee</td><td>5</td>
</tr>
<tr>
<td>...</td><td>...</td><td>...</td>
</tr>
</tbody>
</table>
可能會(huì)有更多行。
編寫代碼,按 ?"name"
? 列對(duì)其進(jìn)行排序。
這個(gè)解決方案雖然很短,但可能看起來(lái)有點(diǎn)難理解,因此,在這里我提供了一些擴(kuò)展性的注釋:
let sortedRows = Array.from(table.tBodies[0].rows) // 1
.sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));
table.tBodies[0].append(...sortedRows); // (3)
對(duì)此算法一步一步進(jìn)行講解:
<tbody>
? 獲取所有 ?<tr>
?。<td>
?(?name
? 字段)中的內(nèi)容進(jìn)行比較。.append(...sortedRows)
? 按正確的順序插入節(jié)點(diǎn)。我們不必刪除行元素,只需要“重新插入”,它們就會(huì)自動(dòng)離開(kāi)原來(lái)的位置。
P.S. 在我們的例子中,表格中有一個(gè)明確的 <tbody>
,但即使 HTML 中的表格沒(méi)有 <tbody>
,DOM 結(jié)構(gòu)也總是具有它。
更多建議: