來看html網頁的代碼和瀏覽器展現的結果(下圖1和圖2):
圖1
圖2
然后對照著下圖的DOM樹,分析DOM的節點層次和定義:
除了html中的<!DOCTYPE>和<meta>聲明外,
1.第一級是html文件中的根元素:<html></html>標簽
2.第二級是根元素html下面的子元素:<head></head>和<body></body>標簽
3.第三級是<head>元素的子元素:<title></title>標簽以及
<body>元素的子元素:<a></a>和<h1></h1>標簽
4.title元素中有一個文本“這是網頁標題”;
a元素中有一個href屬性和一個文本“這是鏈接”;
h1元素中有一個文本“這是網頁內容中的標題”。
綜上:
1.html文檔是一個文檔節點,
2.每個html元素是元素節點,
3.html元素內的文本是文本節點,
4.每個html屬性是屬性節點。
節點的層級關系用術語來描述:
父(parent)、子(child)和同胞(sibling)等。
在節點樹中,頂端節點被稱為根(root),在頁面中對應的是<html></html>標簽,
每個節點都有父節點、除了根(它沒有父節點),<head></head>和<body></body>的父節點就是<html></html>,
一個節點可擁有任意數量的子節點,<body></body>的子節點有<a></a>和<h1></h1>,
同胞是擁有相同父節點的節點,<a></a>和<h1></h1>有相同的父節點,因此它倆就是同胞。
說明: 任何一個html文檔都可以使用DOM將其表示成一個由節點構成的層級結構,當然,節點的類型很多,這也使得構成的html文檔可以各種各樣
<!-- 最基本的HTML片段 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body></body>
</html>
如果以層級結構進行描述,在每個html文檔中,其根節點只有一個,那就是document,其次,根節點存在唯一子節點是<html>元素,這個元素成為文檔元素,在html頁面里面,文檔元素就是<html>元素,并且有且只有一個,在DOM 中總共有12種節點類型,這些類型都繼承一種基本類型。
說明: 最開始的DOM描述了Node的接口,這個是每個DOM節點必須實現的,在JavaScript中將其設計成Node類型,每個節點都繼承這個類型,節點類型由12個常量表示
Node.ELEMENT_NODE:1 Node.ATTRIBUTE_NODE:2 Node.TEXT_NODE:3 Node.CDATA_SECTION_NODE:4 Node.ENTITY_REFERENCE_NODE:5 Node.ENTITY_NODE:6 Node.PROCESSING_INSTRUCTION_NODE:7 Node.COMMENT_NODE:8 Node.DOCUMENT_NODE:9 Node.DOCUMENT_TYPE_NODE:10 Node.DOCUMENT_FRAGMENT_NODE:11 Node.NOTATION_NODE:12
這樣一個節點的類型可通過與這些常量比較來確定,而這個數值可以使用元素節點.nodeType來獲取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello, World!</h1>
<script>
// 獲取h1這個節點
let titleElement=document.querySelector("h1");
// 下面這兩個值是相等的,這樣就可以確定這是一個元素節點了
console.log(titleElement.nodeType);
console.log(titleElement.ELEMENT_NODE);
</script>
</body>
</html>
說明: 這兩個屬性保存著有關節點的信息,但屬性的值完全取決于節點的類型,對元素 而言,nodeName 始終等于元素的標簽名,而 nodeValue 則始終為 null
// 以上面的html為例:
let titleElement=document.querySelector("h1");
console.log(titleElement.nodeName); // h1
console.log(titleElement.nodeValue); // null
說明: 文檔內部的節點都會與其他節點存在關系,一般誰在外層誰是父,同層就是兄,以下面的片段為例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
</body>
</html>
<body>元素是<html>元素的子元素,而<html>元素則是<body>元素的父元,<head>元素是<body>元素的同胞元素,因為它們有共同的父元素<html>
let titleElement=document.querySelector("h1");
console.log(titleElement.childNodes[0]);
console.log(titleElement.childNodes.item(0));
console.log(titleElement.childNodes);
每個節點都有一個childNodes屬性,其中包含一個NodeList的實例,這是一個類數組的對象,用于存儲可以按位置存取的有序節點,可以使用[]或者item()來訪問,最后,NodeList是實時的活動對象,而不是第一次訪問時所獲得內容的快照,需要將其轉換成數組的時候可以使用Array.from來完成,其次,如果需要查詢一個元素是否有子元素,可以使用hasChildNodes()
每一個節點都有一個parentNode屬性,表示節點的父元素,那么上面的childNodes中的每個元素都存在相同的父元素,并且它們之間以兄弟相稱,可以使用previousSibling(上一個元素)和nextSibling(下一個元素)來回切換,如果切換不了就是null,firstChild和lastChild分別指向childNodes中的第一個和最后一個子節點,如果只有一個子元素,它們相等,如果沒有子元素,那么都是null
Element Traversal API新增屬性:
childElementCount:返回子元素數量(不包含文本節點和注釋); firstElementChild:指向第一個 Element 類型的子元素(舊版為firstChild); lastElementChild:指向最后一個 Element 類型的子元素(舊版為 lastChild); previousElementSibling:指向前一個 Element 類型的同胞元素(舊版為 previousSibling); nextElementSibling:指向后一個 Element 類型的同胞元素(舊版為nextSibling)。
appendChild(添加的節點):在 childNodes 列表末尾添加節點,這個方法會返回新添加的節點,如果傳遞的節點是已經存在的節點,那么這個節點就會從原來的位置轉移到新的位置 insertBefore(插入的節點,參照的節點):用于插入節點,將插入的節點會變成參照節點的前一個兄弟節點,并返回,如果參照節點是null,則與第一個方法一致 replaceChild(插入的節點,替換的節點):替換的節點會被返回并從文檔 樹中完全移除,要插入的節點會取而代之 removeChild(移除的節點):將指定的節點刪除,其返回值是這個刪除的節點
注意: 并非所有節點類型都有子節點,如果在不支持子節點的節點上調用 這些方法,則會導致拋出錯誤
cloneNode(是否深度復制):用于復制節點,如果傳true,進行深度復制,那么其子節點也會被復制,傳false則只會復制本身而已 normalize():用于處理文本節點,在遍歷其所有子節點的時候,如果發現空文本節點,則將其刪除;如果兩個同胞節點是相鄰的,則將其合并為一個文本節點
說明: Document類型表示文檔節點的類型,文檔對象document是 HTMLDocument的實例,它表示HTML頁面,document是window對象的屬性,因此是一個全局對象
Document 類型的節點的特征:
nodeType:9; nodeName:"#document"; nodeValue:null; parentNode:null; ownerDocument:null; 子節點:DocumentType(最多一個)、Element(最多一個)、ProcessingInstruction 或 Comment 類型。
document.documentElement:返回HTML頁面中所有的元素 document.body:返回body中所有的元素 document.doctype:獲取文檔最開頭的東西
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello, World!</h1>
<script>
let titleElement=document.querySelector("h1");
console.log(document.documentElement);
console.log(document.body);
console.log(document.doctype);
</script>
</body>
</html>
出現在元素外面的注釋也是文檔的子節點,它們的類型是Comment
document.title:包含<title>元素中的文本,通常顯示在瀏覽器窗口或標簽頁的標題欄,內容可以通過這個屬性進行更改,修改也會反映在頁面上,但是修改title屬性并不會改變元素<title>里面的內容 document.URL:地址欄中的 URL(只讀) document.domain:頁面的域名,在設置值的時候不能存在URL中不包含的值,最后,新設置的值不能比舊的值長,否則會導致錯誤 document.referrer:包含鏈接到當前頁面的那個頁面的URL,如果沒有就是''(只讀),
說明: 在操作DOM的時候最常見的操作就是獲取某個或者某組元素的引用,然后對它們執行某些操作
document.getElementById(元素的ID):查找帶有指定ID的元素(ID值需要完全匹配才可以),如果找到就返回這個元素,沒找到就返回null,如果查找的ID元素存在多個,只返回第一個 document.getElementsByTagName(元素的標簽名):尋找符合標簽名的元素,其返回值是一個HTMLCollection對象,它與NodeList相似,所以可以使用相同的[]和item()方法來獲取指定的元素,這個對象還存在一個namedItem()的方法,通過標簽的name屬性獲取某一項的引用,對于 name 屬性的元素,還可以直接使用中括號來獲取,最后就是這個方法如果傳入*,表示匹配一切字符 document.getElementsByName(name屬性值):尋找滿足條件name屬性值的元素,返回值也是一個HTMLCollection對象,那么使用起來跟上一個方法相差不大
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<img src="myimage.gif" name="myImage" />
<script>
let imgElement=document.getElementsByTagName("img");
console.log(imgElement);
console.log(imgElement[0]);
console.log(imgElement.item(0));
console.log(imgElement.namedItem("myImage"));
console.log(imgElement["myImage"]);
</script>
</body>
</html>
對于HTMLCollection對象而言,中括號既可以接收數值索引,也可以接收字符串索引。而在后臺, 數值索引會調用item(),字符串索引會調用namedItem()。
document.forms:查找文檔中所有<form>元素,返回值是HTMLCollection對象 document.images:查找文檔中所有<img>元素,返回值是HTMLCollection對象 document.links:查找文檔中所有帶href屬性的<a>元素,返回值是HTMLCollection對象
document.write('字符串'):在頁面加載期間向頁面中動態添加內容,一般用于動態包含外部資源 document.writeln('字符串'):在頁面加載期間向頁面中動態添加內容并且在末尾加一個\n,一般用于動態包含外部資源 document.open():打開網頁輸出流,在node中使用的比較多 document.close():關閉網頁輸出流,在node中使用的比較多
如果是在頁面加載完畢再去動態的去寫入,則寫入的內容會重寫整個頁面
說明: 它暴露出訪問元素標簽名、子節點和屬性的能力
Element類型的節點的特征:
nodeType:1; nodeName:元素的標簽名; nodeValue:null; parentNode:Document 或 Element 對象; 子節點的類型: Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference。
對于標簽名的獲取,可以使用節點.nodeName或者節點.tagName來獲取,不過,在HTML中使用的時候,獲取到的結果都是以大寫形式的標簽名,在XML中,獲取的與源代碼中標簽名的大小寫一致,使用的時候需要注意
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<img src="myimage.gif" name="myImage" />
<script>
let imgElement=document.getElementsByName("myImage");
console.log(imgElement[0].nodeName);
console.log(imgElement[0].tagName);
</script>
</body>
</html>
說明: 所有的HTML元素都可以通過HTMLElement類型表示,包括實例,另外,HTMLElement直接繼承了Element并增加了以下屬性,這些屬性是每個HTML 元素都存在的屬性
id:元素在文檔中的唯一標識符; title:包含元素的額外信息,通常以提示條形式展示; lang:元素內容的語言代碼(很少用); dir:語言的書寫方向("ltr"表示從左到右,"rtl"表示從右到左); className:相當于 class 屬性,用于指定元素的 CSS 類
可以用對應的屬性修改相應的值,不過修改id和lang對用戶是不可見的,修改title只有在鼠標移到元素上面才反應出來
節點.getAttribute('需要獲取的屬性名'):返回這個節點上面指定屬性名對應的屬性值
console.log(imgElement[0].getAttribute("name"));
傳遞的屬性名應該與它們實際的屬性名是一樣的,如果搜索的屬性名不存在,返回值是null,此外,屬性名沒有大小寫之分,最后,當使用DOM對象訪問屬性的時候,在訪問style和事件的時候其返回值與getAttribute的返回值是存在區別的
節點.setAttribute('設置的屬性名','屬性的值'):如果屬性存在,則屬性值會被替換成新的,如果不存在,則會創建這個屬性,此外,這個方法在設置的屬性名會規范為小寫形式,同時對于自定義屬性,并不會將其添加到元素的屬性上面去 節點.removeAttribute('需要刪除的屬性'):將指定的屬性從元素上面刪除
document.createElement('創建元素的標簽名'):創建一個新元素,注意HTML不存在大小寫,而XML存在,其次,在創建新元素的同時也會將ownerDocument屬性設置為 document。 此時,可以再為其添加屬性、添加更多子元素,不過,如果這個元素沒有被添加到文檔中去,添加再多的屬性也是依附在元素上的信息,在瀏覽器上并不會渲染出來
說明: Text節點由Text類型表示,也就是文本內容,一般包含在標簽內部,通常使用childNodes獲取,另外,這種節點不包含HTML代碼
Text類型的節點的特征:
nodeType:3 nodeName:"#text" nodeValue:節點中包含的文本 parentNode:Element 對象 子節點的類型: 沒有
這種節點的內容一般使用nodeValue屬性訪問,也可以使用data屬性訪問,不過很少使用,他們包含的值是相同的,這兩個屬性加上.length就可以得到文本節點包含的字符數了
文本節點操作方法:
appendData(text):向節點末尾添加文本 text; deleteData(offset, count):從位置 offset 開始刪除 count 個字符; insertData(offset, text):在位置 offset 插入 text; replaceData(offset, count, text):用 text 替換從位置 offset 到 offset count的文本; normalize:當一個節點存在多個文本節點的時候,可以使用這個方法將其合并成一個字符串 splitText(offset):在位置offset將當前文本節點拆分為兩個文本節點; substringData(offset, count):提取從位置 offset 到 offset + count 的文本。 length:獲取文本節點包含的字符數
console.log(imgElement.childNodes.item(0).nodeValue);
console.log(imgElement.childNodes.item(0).nodeValue.length);
console.log(imgElement.childNodes.item(0).data);
console.log(imgElement.childNodes.item(0).data.length);
文本內容的每個元素最多只能有一個文本節點,另外在修改文本節點的時候,小于號、大于號或引號會被轉義
document.createTextNode('文本節點的內容'):創建一個文本節點,當然,創建的內容中小于號、大于號或引號會被轉義,一般來說一個元素只包含一個文本子節點。不過,也可以讓元素包含多個文本子節點
let element=document.createElement("div");
element.className="message";
let textNode=document.createTextNode("Hello world!");
element.appendChild(textNode);
let anotherTextNode=document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
在將一個文本節點作為另一個文本節點的兄弟元素插入,兩個文本節點的文本之間 不包含空格
說明: 這是一個注釋類型,與Text 類型相似,除了沒有splitText這個方法以外操作上是一致的,在創建的時候可以通過document.createComment('注釋的內容')來創建,最后,瀏覽器不承認結束的</html>標簽之后的注釋。如果要訪問注釋節點,則必須確定它們是</html>元素的后代
Comment類型的節點的特征:
nodeType:8 nodeName:"#comment" nodeValue:注釋的內容 parentNode:Document 或 Element 對象 子節點的類型: 沒有
說明: 它表示XML中特有的CDATA區塊,同時繼承Text 類型,因此擁有其擁有的方法,在真正的XML文檔中,可以使用document.createCDataSection()并傳入節點內容來創建CDATA區塊
CDATASection類型的節點的特征:
nodeType:4 nodeName:"#cdata-section" nodeValue:CDATA 區塊的內容 parentNode:Document 或 Element 對象 子節點的類型: 沒有
說明: DocumentType對象不支持動態創建,只能在解析文檔代碼時創建,其次,文檔類型可以通過document.doctype來獲取,在這個對象中存在三個屬性:name、entities和notations,name是文檔類型的名稱,就是近跟在!DOCTYPE后面的文本,結束符是空格,entities是這個文檔類型描述的實體的NamedNodeMap,而 notations是這個文檔類型描述的表示法的NamedNodeMap。因為瀏覽器中的文檔通常是HTML或XHTML文檔類型,所以entities和notations列表為空,在DOM2的時候擴展了三個屬性:publicId、systemId和internalSubset,取值如下示例
DocumentType類型的節點的特征:
nodeType:10 nodeName:文檔類型的名稱 nodeValue:null parentNode:Document 對象 子節點的類型: 沒有
<!DOCTYPE html PUBLIC "-// W3C// DTD XHTML 1.0 Strict// EN" "http://www.w3.org/TR/xhtml1/DTDxhtml1-strict.dtd" [<!ELEMENT name (#PCDATA)>
] >
document.doctype.name // html
document.doctype.publicId // -// W3C// DTD HTML 4.01// EN
document.doctype.systemId // "http://www.w3.org/TR/ html4/strict.dtd"
document.doctype.internalSubset // "<!ELEMENT name (#PCDATA)>"
說明: 是一種特殊的節點類型,它允許你在內存中創建一個文檔片段,然后將其他節點附加到該片段中。文檔碎片不是真實DOM樹的一部分,因此對其進行操作不會觸發頁面重繪,這樣可以提高性能并減少DOM操作的成本,它可以通過document.createDocumentFragment()來創建
DocumentFragment類型的節點的特征:
nodeType:11 nodeName:#document-fragment nodeValue:null parentNode:null 子節點的類型:Element、ProcessingInstruction、Comment、Text、CDATASection 或 EntityReference
<ul id="myList"></ul>
// 給ul添加三個li
let fragment=document.createDocumentFragment();
let ul=document.getElementById("myList");
for (let i=0; i < 3; ++i) {
let li=document.createElement("li");
li.appendChild(document.createTextNode(`Item ${i + 1}`));
fragment.appendChild(li);
}
ul.appendChild(fragment);
說明: <script>這個標簽用于向網頁添加JavaScript代碼,可以通過src屬性引入外部的JavaScript文件,也可以是元素內容的源代碼,動態加載腳本就是頁面加載時不存在,之后通過DOM引入的JavaScript
// 假設需要向頁面加入一個foo.js的腳本
let script=document.createElement("script");
script.src="foo.js";
document.body.appendChild(script);
// 如果需要插入代碼加載腳本
<script>
function sayHi() {
alert("hi");
}
</script>
// 通過DOM操作改寫上面的HTML片段
let script=document.createElement("script");
script.appendChild(
document.createTextNode(
"function sayHi(){
alert('hi');
}"
)
);
document.body.appendChild(script);
說明: 這個與上面是類似的,只不過加載的內容不同,一種使用<link>引入外部文件,一種是用<style>寫樣式代碼,動態同樣也是頁面初始加載不存在,后面才加上去的
<!-- 假設加載一個styles.css的文件 -->
<link rel="stylesheet" type="text/css" href="styles.css">
// 通過DOM操作改寫
let link=document.createElement("link");
link.rel="stylesheet";
link.type="text/css";
link.href="styles.css";
let head=document.getElementsByTagName("head")[0];
head.appendChild(link);
<!-- 另一種通過style加載css規則 -->
<style type="text/css">
body {
background-color: red;
}
</style>
// 使用DOM操作改寫
let style=document.createElement("style");
style.type="text/css";
style.appendChild(
document.createTextNode(
"body{
background-color:red
}"
)
);
let head=document.getElementsByTagName("head")[0];
head.appendChild(style);
對于IE的瀏覽器,操作style節點的時候需要使用這個節點的styleSheet屬性的cssText屬性,給這個屬性設置css樣式字符串就可以了
最后,需要注意,NodeList對象和相關的NamedNodeMap、HTMLCollection其保存的值會隨著節點的變化而變化,所以使用的時候需要注意
說明: MutationObserver是一個用于監視DOM樹變化的接口。它可以用來觀察DOM節點的插入、刪除、屬性的變化等變動,并在這些變動發生時執行特定的回調函數,MutationObserver的實例要通過調用MutationObserver構造函數并傳入一個回調函數來創建
// 這樣就創建了一個觀察者observer
let observer=new MutationObserver(
()=> console.log('<body> attributes changed')
);
說明: 上面創建的這個觀察者實例并不會關聯DOM的任何部分,如果需要,則需要使用observe方法,它有兩個參數,第一個是需要觀察的DOM節點(必須),第二個是一個配置對象(可選),在使用這個方法之后,被監聽的元素上面的屬性發生變化的時候都會異步的執行注冊的回調函數,后代元素的屬性更改并不會觸發
配置對象的參數:
subtree:當為 true 時,將會監聽以 target 為根節點的整個子樹。包括子樹中所有節點的屬性,而不僅僅是針對 target。默認值為 false。 childList:當為 true 時,監聽 target 節點中發生的節點的新增與刪除(同時,如果 subtree 為 true,會針對整個子樹生效)。默認值為 false。 attributes:當為 true 時觀察所有監聽的節點屬性值的變化。默認值為 true,當聲明了 attributeFilter 或 attributeOldValue,默認值則為 false。 attributeFilter:一個用于聲明哪些屬性名會被監聽的數組。如果不聲明該屬性,所有屬性的變化都將觸發通知。 attributeOldValue:當為 true 時,記錄上一次被監聽的節點的屬性變化;可查閱監聽屬性值了解關于觀察屬性變化和屬性值記錄的詳情。默認值為 false。 characterData:當為 true 時,監聽聲明的 target 節點上所有字符的變化。默認值為 true,如果聲明了 characterDataOldValue,默認值則為 false characterDataOldValue:當為 true 時,記錄前一個被監聽的節點中發生的文本變化。默認值為 false
observer.observe(document.body, { attributes: true });
document.body.className='foo';
console.log('Changed body class');
這也證明注冊函數的執行是異步的
說明: MutationObserver實例中注冊的回調函數的參數有兩個,都是可選的,一個是MutationRecord 實例的數組,它包含的息包括發生了什么變化,以及 DOM 的哪一部分受到了影響,此外,連續的修改會生成多個實例,在最后一次修改后一次性按順序返回回來,另一個是觀察變化的MutationObserver的實例,這個主要觀察屬性的變化
MutationRecord實例的屬性:
target:被修改影響的目標節點 type: 字符串,表示變化的類型:"attributes"、"characterData"或"childList" oldValue: 如果在 MutationObserverInit 對象中啟用,"attributes"或"characterData"的變化事件會設置這個屬性為被替代的值 "childList"類型的變化始終將這個屬性設置為 null attributeName: 對于"attributes"類型的變化,這里保存被修改屬性的名字 其他變化事件會將這個屬性設置為 null attributeNamespace: 對于使用了命名空間的"attributes"類型的變化,這里保存被修改屬性的名字 其他變化事件會將這個屬性設置為 null addedNodes: 對于"childList"類型的變化,返回包含變化中添加節點的 NodeList 默認為空 NodeList removedNodes: 對于"childList"類型的變化,返回包含變化中刪除節點的 NodeList 默認為空 NodeList previousSibling:對于"childList"類型的變化,返回變化節點的前一個同胞 Node 默認為 null nextSibling:對于"childList"類型的變化,返回變化節點的后一個同胞 Node 默認為 null
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
observer.observe(document.body, { attributes: true });
document.body.setAttribute('foo', 'bar');
// 連續更改
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
observer.observe(document.body, { attributes: true });
document.body.className='foo';
document.body.className='bar';
document.body.className='baz';
說明: 一般情況下,只要被監聽的元素沒有被垃圾回收,那么MutationObserver中注冊的回調函數就會在屬性變化的時候執行一次,如果需要這個回調函數失效,可以使用disconnect()這個方法,它會取消之前加入隊列和之后加入隊列的回調函數,也就是停止觀察
let observer=new MutationObserver(
()=> console.log('<body> attributes changed')
);
observer.observe(document.body, { attributes: true });
document.body.className='foo';
observer.disconnect();
document.body.className='bar';
希望斷開與觀察目標的聯系,但又希望處理由于調用disconnect()而被拋棄的記錄隊列中的MutationRecord實例,可以使用takeRecords()清空記錄隊列,取出里面的實例
說明: 多次調用observe(),可以使用一個創建的觀察者實例觀察多個目標節點,這個過程可以通過MutationRecord參數的target屬性觀察
let observer=new MutationObserver(
(mutationRecords)=>
console.log(
mutationRecords.map(
(x)=> x.target
)
)
);
// 向頁面主體添加兩個子節點
let childA=document.createElement('div'),
childB=document.createElement('span');
document.body.appendChild(childA);
document.body.appendChild(childB);
// 觀察兩個子節點
observer.observe(childA, { attributes: true });
observer.observe(childB, { attributes: true });
// 修改兩個子節點的屬性
childA.setAttribute('foo', 'bar');
childB.setAttribute('foo', 'bar');
說明: 使用disconnect會停止觀察者中的回調函數,但是其生命并未結束,可以重新使用observe將其關聯到新的節點將其重啟
let observer=new MutationObserver(
()=> console.log('<body> attributes changed')
);
observer.observe(document.body, { attributes: true });
// 停止回調
observer.disconnect();
// 這個不會觸發
document.body.className='bar';
// 重啟
observer.observe(document.body, { attributes: true });
// 照常觸發
document.body.className='bar';
說明: 這個接口的設計用于性能優化,其核心是異步回調與記錄隊列模型,為了在大量變化事件發生時不影響性能,每次變化的信息(由觀察者實例決定)會保存在 MutationRecord實例中,然后添加到記錄隊列。這個隊列對每個 MutationObserver實例都是唯一的,每次MutationRecord被添加到 MutationObserver的記錄隊列時,僅當之前沒有已排期的微任務回調時,才會將觀察者注冊的回調作為微任務調度到任務隊列上。這樣可以保證記錄隊列的內容不會被回調處理兩次,回調執行后,這些MutationRecord就用不著了, 因此記錄隊列會被清空,其內容會被丟棄
說明: 這個對象用于控制對目標節點的觀察范圍,看上去很高級,其實就是observe這個函數的第二個參數,參數的具體內容在這個函數這里有寫到
在調用observe()時,MutationObserverInit 對象中的 attribute、characterData 和 childList 屬性必須至少有一項為 true,否則會拋出錯誤,因為沒有任何變化事件可能觸發回調,
說明: MutationObserver可以觀察節點屬性的添加、移除和修改。要為屬性變化注冊回調,需要在MutationObserverInit對象中將attributes屬性設置為true
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
observer.observe(document.body, { attributes: true });
// 添加屬性
document.body.setAttribute('foo', 'bar');
// 修改屬性
document.body.setAttribute('foo', 'baz');
// 移除屬性
document.body.removeAttribute('foo');
如果想觀察某個或某幾個屬性,可以使用attributeFilter屬性來設置白名單來進行過濾
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
observer.observe(document.body, { attributeFilter: ['foo'] });
// 添加白名單屬性
document.body.setAttribute('foo', 'bar');
// 添加被排除的屬性
document.body.setAttribute('baz', 'qux');
說明: MutationObserver可以觀察文本節點中字符的添加、刪除和修改。要為字符數據注冊回調,需要在MutationObserverInit對象中將characterData屬性設置為true
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
// 創建要觀察的文本節點
document.body.firstChild.textContent='foo';
observer.observe(document.body.firstChild, { characterData: true });
// 賦值為相同的字符串
document.body.firstChild.textContent='foo';
// 賦值為新字符串
document.body.firstChild.textContent='bar';
// 通過節點設置函數賦值
document.body.firstChild.textContent='baz';
說明: MutationObserver可以觀察目標節點子節點的添加和移除。要觀察子節點,需要在MutationObserverInit對象中將childList屬性設置為true
// 假設需要交換兩個子節點的位置
document.body.innerHTML='';
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
// 創建兩個初始子節點
document.body.appendChild(document.createElement('div'));
document.body.appendChild(document.createElement('span'));
observer.observe(document.body, { childList: true });
// 交換子節點順序(先刪除后添加)
document.body.insertBefore(document.body.lastChild, document.body.firstChild);
說明: 默認情況下,MutationObserver將觀察的范圍限定為一個元素及其子節點的變化。可以把觀察的范圍擴展到這個元素的子樹(所有后代節點),這需要在 MutationObserverInit對象中將subtree屬性設置為true
注意:被觀察子樹中的節點被移出子樹之后仍然能夠觸發變化事件。這意味著在子樹中的節 點離開該子樹后,即使嚴格來講該節點已經脫離了原來的子樹,但它仍然會觸發變化事件
// 清空主體
document.body.innerHTML='';
let observer=new MutationObserver(
(mutationRecords)=> console.log(mutationRecords)
);
let subtreeRoot=document.createElement('div'),
subtreeLeaf=document.createElement('span');
// 創建包含兩層的子樹
document.body.appendChild(subtreeRoot);
subtreeRoot.appendChild(subtreeLeaf);
// 觀察子樹
observer.observe(subtreeRoot, { attributes: true, subtree: true });
// 把節點轉移到其他子樹
document.body.insertBefore(subtreeLeaf, subtreeRoot);
subtreeLeaf.setAttribute('foo', 'bar');
說明: 這個方法接收一個CSS選擇符參數,也就是在寫樣式的時候,怎么寫選擇器,這里就怎么寫,它會返回匹配該模式的第一個元素,如果匹配不成功則返回null
如果是在document上用,則會從文檔元素開始搜索,如果在element上用,則只從當前元素后代中搜索
// 取得<body>元素
let body=document.querySelector("body");
// 取得 ID 為"myDiv"的元素
let myDiv=document.querySelector("#myDiv");
// 取得類名為"selected"的第一個元素
let selected=document.querySelector(".selected");
說明: 這個與上面那個是相似的,只不過它會返回所有匹配到的元素,說白了就是一個NodeList對象,只不過這個對象是靜態的,在取值上面,可以通過for-of 、item()方法或中括號語法取元素
// 取得 ID 為"myDiv"的<div>元素中的所有<em>元素
let ems=document.getElementById("myDiv").querySelectorAll("em");
// 取得所有類名中包含"selected"的元素
let selecteds=document.querySelectorAll(".selected");
// 取得所有是<p>元素子元素的<strong>元素
let strongs=document.querySelectorAll("p strong");
說明: 這個方法有點服務于上面兩個方法的意為,作用是為了查找是否存在元素滿足一段css選擇符的選擇,滿足就返回true,否則就是false
// 檢測body中是否存在class為page1的元素
if (document.body.matches("body .page1")){
}
說明: 它存在于document對象和所有HTML元素上,它接受一個或多個類名組合而成的字符串,類名之間用空格隔開(類名的順序無關緊要),在document中調用則會返回文檔中所有匹配的元素,如果在某個元素上調用則會返回它后代中匹配的所有元素
// 取得所有類名中包含"username"和"current"元素
// 這兩個類名的順序無關緊要
let allCurrentUsernames=document.getElementsByClassName("username current");
說明: 以前操作屬性,可以通過className完成屬性的添加、刪除、替換,因為這個屬性是一個字符串,所以更改這個值之后,需要重新設置這個值才算完成更改,在HTML5里面,新增加了classList這個屬性,它簡化了這些操作,它的返回值是一個DOMTokenList的實例。與其他集合類型一樣,DOMTokenList也有length屬性表示自己包含多少項,也可以通過item()或中括號取得個別的元素。
DOMTokenList的實例新增屬性:
add(value):向類名列表中添加指定的字符串值 value。如果這個值已經存在,則什么也不做。 contains(value):返回布爾值,表示給定的 value 是否存在。 remove(value):從類名列表中刪除指定的字符串值 value。 toggle(value):如果類名列表中已經存在指定的 value,則刪除;如果不存在,則添加。
<!-- 假設存在這樣的節點 -->
<div class="bd user disabled">...</div>
// 獲取這個節點
let divElement=document.getElementsByTagName("div");
// 通過className屬性獲取這個節點的class,拿到之后對其操作后
// 需要重新對className屬性賦值
// 要刪除"user"類
let targetClass="user";
// 把類名拆成數組
let classNames=divElement.className.split(/\s+/);
// 找到要刪除類名的索引
let idx=classNames.indexOf(targetClass);
// 如果有則刪除
if (idx > -1) {
classNames.splice(i,1);
}
// 重新設置類名
divElement.className=classNames.join(" ");
// 通過classList屬性進行操作
// 刪除"disabled"類
div.classList.remove("disabled");
// 添加"current"類
div.classList.add("current");
// 切換"user"類
div.classList.toggle("user");
document.activeElement: 它保存當前頁面獲取焦點的元素,由于不同同時讓多個節點獲取焦點,那也就是這個值只會保留最后一個獲取焦點的元素,在頁面沒完全加載完前,它的值是null,在完全加載完之后,值是document.body document.hasFocus:用于檢測文檔是否擁有焦點,也就是用戶是否正在進行交互,它的返回值是布爾值,表示存在或者不存在 節點.foucs:執行這個方法可以讓某個節點獲取焦點
說明: 它表示文檔加載的狀態,它的值有下面兩個
loading:表示文檔正在加載 complete:表示文檔加載完成
說明: 它表示瀏覽器當前處于什么渲染模式
標準模式:CSS1Compat 混雜模式:BackCompat
說明: 可以直接使用document.head來獲取<head>元素
說明: 它表示文檔實際使用的字符集,也可以用來指定新字符集,默認值是UTF-16
說明: 這個操作是html5允許的操作,但要使用前綴data-以便告訴瀏覽器,這些屬性既不包含與渲染有關的信息,也不包含元素的語義信息,不過命名沒有什么要求,在設置完成后,可以通過dataset屬性,它的值是一個DOMStringMap的實例,包含一組鍵/值對映射,在取值時使用data-后面所有的字符拼接成一個字符串,這個字符串是小寫的來取值
<div id="myDiv" data-appId-MaMaMaMaMa="12345" data-myname="Nicholas"></div>
let div=document.getElementById("myDiv");
console.log(div.dataset);
說明: 在讀取的時候,它會返回元素所有后代的HTML字符串,在設置的時候,默認是HTML,所以設置一個字符串值的時候,會將其轉換成HTML片段,前者是只能操作節點的子元素,后者則能操作節點本身和其子元素
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
let divElement=document.getElementById("content");
console.log(divElement.innerHTML);
divElement.innerHTML="Hello world!";
說明: 它們表示在指定的位置插入HTML或者文本,它們都有兩個參數,第一個是插入的位置選項,第二個是插入的內容
插入位置選項(無大小寫之分):
beforebegin:插入當前元素前面,作為前一個兄弟節點; afterbegin:插入當前元素內部,作為新的子節點或放在第一個子節點前面; beforeend:插入當前元素內部,作為新的子節點或放在最后一個子節點后面; afterend:插入當前元素后面,作為下一個兄弟節點。
對于選項中的前面和后面的標準:
假設當前元素是<p>Hello world!</p>,則beforebegin和afterbegin中的begin指開始標簽<p>,而 afterend和beforeend中的end指結束標簽</p>。
說明: 它可以讓指定的元素滾動到可視區域,它接受一個參數,這個參數可以是一個布爾值,也可以是一個配置對象
布爾值:
true: 窗口滾動后元素的頂部與視口頂部對齊 false: 窗口滾動后元素的頂部與視口底部對齊
配置對象:
behavior: 滾動是否平滑,取值存在三個, smooth:平滑滾動 instant:通過一次跳躍立刻發生 auto:通過scroll-behavior的計算來得出,默認取值是這個 block:定義垂直方向的對齊,可取的值為"start"、"center"、"end"和"nearest",默 認為 "start"。 inline:定義水平方向的對齊,可取的值為"start"、"center"、"end"和"nearest",默 認為 "nearest"。
說明: 支持style屬性的html元素在其節點對象上面會存在一個style的對象,它是一個CSSStyleDeclaration類型的實例,在寫css屬性的時候兩個單詞使用連字符-鏈接,但是在對象中就需要駝峰命名了,一般來說,如果需要,獲取到了節點,就可以用這個對象進行樣式操作,同時也能獲取通過style屬性設置的樣式,注意Float這個css屬性,他在style對象中對應的值是cssFloat,而不是float,因為這個在JavaScript是關鍵字
let myDiv=document.getElementById("myDiv");
// 設置背景顏色
myDiv.style.backgroundColor="red";
// 修改大小
myDiv.style.width="100px";
myDiv.style.height="200px";
// 設置邊框
myDiv.style.border="1px solid black";
在標準模式下,所有尺寸必須帶單位,在混雜模式下,尺寸不帶單位默認是px
說明: 由于style對象只能獲取到style屬性中的信息,對于寫在<style>里面的并沒有什么辦法,這時可以使用document.defaultView.getComputedStyle(計算樣式的元素, 偽元素字符串)來完成,它的返回值類型跟上面那個是一樣的,不過它包括<style>里面的樣式
<!DOCTYPE html>
<html>
<head>
<title>Computed Styles Example</title>
<style type="text/css">
#myDiv {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div
id="myDiv"
style="background-color: red; border: 1px solid black"
></div
<script>
let myDiv=document.getElementById("myDiv");
let computedStyle=document.defaultView.getComputedStyle(myDiv, null);
console.log(myDiv.style.width);
console.log(computedStyle.width);
</script>
</body>
</html>
這個返回對象里面的值是不能夠更改的,同時,如果瀏覽器存在默認的樣式,也會在這個返回的對象中展示出來
注意: 下面這組屬性是只讀的,每次訪問都會重新計算,所以一般情況避免多次使用,影響性能
節點.offsetParent:返回離當前元素最近的已定位的父元素,如果沒有找到這樣的父元素,則返回最近的祖先元素。 節點.offsetLeft:返回當前元素左上角相對于offsetParent節點的左邊界偏移的像素值 節點.offsetHeight:它返回該元素的像素高度,高度包含該元素的垂直內邊距和邊框和水平滾動條,且是一個整數 節點.offsetTop:返回當前元素相對于其offsetParent元素的頂部內邊距的距離 節點.offsetWidth:返回一個元素的布局寬度,這個寬度包括元素的寬度、內邊距、邊框以及垂直滾動條
說明: 它只元素內部的內容,所以不會包含滾動條的寬度和高度,同時也是只讀,避免重復調用
節點.clientHeight:返回元素內部的高度,包含內邊距 節點.clientWidth:返回元素內部的寬度,包含內邊距
注意: 下面這組屬性是只讀的,每次訪問都會重新計算,所以一般情況避免多次使用,影響性能
節點.scrollHeight:返回一個元素的高度,包括內邊距以及溢出不可見的部分 節點.scrollLeft:返回內容+內邊距的區域舉例左側的像素值,也就是左側隱藏的像素值,當然這個值也可以自己來設置 節點.scrollTop:返回在垂直方向上舉例頂部的像素值,其它與上面那個屬性是一樣的 節點.scrollWidth:返回一個元素的寬度,包括內邊距以及溢出不可見的部分
當然,如果想快捷的確定元素的尺寸可以使用getBoundingClientRect()這個方法,它會給出元素在頁面中相對于視口的位置
說明: 你在電腦上面用鼠標一按一拉,將一些文字圈起來,此時這些文字會被選中,如果你想操作這塊區域,這時范圍(range)就可以使用了,它一般是用來出來文檔中選擇的文本,讓其處理的時候更加簡單,當然,它也是可以處理節點元素的
說明: 可以使用document.createRange()創建一個范圍,與節點類似,在這個文檔中創建的范圍不可以在另一個文檔中去使用,每個范圍都是一個Range的實例,包含以下屬性和方法:
collapsed:返回一個表示范圍的起始位置和終止位置是否相同的布爾值 commonAncestorContainer:返回文檔中以startContainer和endContainer為后代的最深的節點 endContainer:表示范圍終點所在的節點 endOffset:表示范圍終點在endContainer中的位置,值是數字 startContainer:表示范圍起點所在的節點 startOffset:表示范圍起點在startContainer中的位置,值是數字
說明: 最簡單使用就是使用selectNode()或selectNodeContents()方,這兩個方法都接收一個節點作為參數,并將該節點的信息添加到調用它的范圍,selectNode()選擇整個節點,包括其后代節點,selectNodeContents()只選擇節點的后代
<!DOCTYPE html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
<script>
let range1=document.createRange(),
range2=document.createRange(),
p1=document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
console.log(range1);
console.log(range2);
</script>
</body>
</html>
選定節點后范圍可執行的方法:
setStartBefore(refNode):把范圍的起點設置到 refNode 之前,從而讓 refNode 成為選區的第一個子節點。startContainer 屬性被設置為 refNode.parentNode,而 startOffset屬性被設置為 refNode 在其父節點 childNodes 集合中的索引。 setStartAfter(refNode):把范圍的起點設置到 refNode 之后,從而將 refNode 排除在選區之外,讓其下一個同胞節點成為選區的第一個子節點。startContainer 屬性被設置為 refNode.parentNode,startOffset 屬性被設置為 refNode 在其父節點 childNodes 集合中的索引加 1。 setEndBefore(refNode):把范圍的終點設置到 refNode 之前,從而將 refNode 排除在選區之外、讓其上一個同胞節點成為選區的最后一個子節點。endContainer 屬性被設置為 refNode. parentNode,endOffset 屬性被設置為 refNode 在其父節點 childNodes 集合中的索引。 setEndAfter(refNode):把范圍的終點設置到 refNode 之后,從而讓 refNode 成為選區的最后一個子節點。endContainer 屬性被設置為 refNode.parentNode,endOffset 屬性被設置為 refNode 在其父節點 childNodes 集合中的索引加 1。
說明: 這里存在setStart(參照節點,偏移量)和setEnd(參照節點,偏移量)兩個方法,它們可以選擇節點的某一部分,這也是其主要的作用,同時setStart的偏移量可以理解成從哪里開始,包括偏移量這個位置,setEnd理解為結束的位置,包括偏移量這個位置
<p id="p1">
<b>Hello</b>
world!
</p>
// 假設需要選擇從"Hello"中的"llo"到" world!"中的"o"的部分
// 獲取相關節點的引用
let p1=document.getElementById("p1")
let helloNode=p1.firstChild.firstChild
let worldNode=p1.lastChild
// 創建范圍
let range=document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
range.deleteContents():刪除范圍包含的節點 range.extractContents():刪除范圍包含的節點,但是會將刪除的部分返回 range.cloneContents():創建一個范圍的副本,然后將這個副本返回,注意返回的不是節點
let p1=document.getElementById("p1"),
helloNode=p1.firstChild.firstChild,
worldNode=p1.lastChild,
range=document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
range.deleteContents();
let p1=document.getElementById("p1"),
helloNode=p1.firstChild.firstChild,
worldNode=p1.lastChild,
range=document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let fragment=range.extractContents();
p1.parentNode.appendChild(fragment);
let p1=document.getElementById("p1"),
helloNode=p1.firstChild.firstChild,
worldNode=p1.lastChild,
range=document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let fragment=range.cloneContents();
p1.parentNode.appendChild(fragment);
range.insertNode(插入的節點):在范圍選區開始的位置插入一個節點,這個一般用于插入有用的信息 range.surroundContents(插入包含范圍的節點):與上面不同的地方在于它可以插入包含范圍的節點,其它是一致的,這個一般用于高亮關鍵詞
let p1=document.getElementById("p1"),
helloNode=p1.firstChild.firstChild,
worldNode=p1.lastChild,
range=document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let span=document.createElement("span");
span.style.color="red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
let p1=document.getElementById("p1"),
helloNode=p1.firstChild.firstChild,
worldNode=p1.lastChild,
range=document.createRange();
range.selectNode(helloNode);
let span=document.createElement("span");
span.style.backgroundColor="yellow";
range.surroundContents(span);
說明: 通過折疊可以將一段文本的范圍折疊成一個點,使范圍的起點和終點合并,將原本包含多個節點的范圍簡化為只包含一個節點或一個位置,從而化簡操作,折疊操作可以交給collapse(布爾值),布爾值表示折疊到范圍哪一端,true表示折疊到起點,false表示折疊到終點,檢測是否折疊的操作可以交給collapsed屬性,其返回值也是布爾值,同時它也能檢測兩個節點是否是相鄰的狀態
相鄰狀態的判定: 在范圍的上下文中,我們使用的是邊界點和偏移量來定義范圍的起點和終點。當范圍中的兩個節點相鄰時,它們的邊界點會非常接近,甚至可能在同一個位置上。這就導致了范圍的起點和終點重疊。
<!DOCTYPE html>
<html>
<head>
<title>折疊將范圍變成一個點</title>
</head>
<body>
<p>這是一個示例文本。</p>
<script>
var range=document.createRange();
var textNode=document.querySelector("p").firstChild;
range.setStart(textNode, 2);
range.setEnd(textNode, 7);
console.log("初始范圍選中文本: " + range.toString());
range.collapse(false);
console.log("折疊后范圍選中文本: " + range.toString());
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>檢測元素是否相鄰</title>
</head>
<body>
<p id="p1">Paragraph 1</p>
<p id="p2">Paragraph 2</p>
<script>
let p1=document.getElementById("p1"),
p2=document.getElementById("p2"),
range=document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
</script>
</body>
</html>
假設p1和p1是不相鄰的兩個節點,那么將其設置成范圍的起始位置和結束位置,那么上面所說的collapsed屬性的值應該是false,但是結果是true,與猜測相反,那也就得到這兩個元素是相鄰的
說明: 如果有多個范圍,則可以使用range.compareBoundaryPoints(常量值,比較的范圍)確定范圍之間是否存在公共的起點或終點,它的返回值是一個數字,第一個范圍的邊界點位于第二個范圍的邊界點之前時返回-1,在兩個范圍的邊界點相等時返回 0,在第一個范圍的邊界點位于第二個范圍的邊界點之后時返回1
常量值取值:
Range.START_TO_START(0):比較兩個范圍的起點; Range.START_TO_END(1):比較第一個范圍的起點和第二個范圍的終點; Range.END_TO_END(2):比較兩個范圍的終點; Range.END_TO_START(3):比較第一個范圍的終點和第二個范圍的起點。
range.cloneRange():這個方法會創建調用它的范圍的副本,新范圍包含與原始范圍一樣的屬性,修改其邊界點不會影響原始范圍
range.detach():把范圍從創建它的文檔中剝離,接觸對范圍的引用,便于垃圾回收將其處理
OM 節點
根據 W3C 的 HTML DOM 標準,HTML 文檔中的所有內容都是節點:
整個文檔是一個文檔節點
每個 HTML 元素是元素節點
HTML 元素內的文本是文本節點
每個 HTML 屬性是屬性節點
注釋是注釋節點
HTML DOM 節點樹
HTML DOM 將 HTML 文檔視作樹結構。這種結構被稱為節點樹:
HTML DOM Tree 實例
通過 HTML DOM,樹中的所有節點均可通過 JavaScript 進行訪問。所有 HTML 元素(節點)均可被修改,也可以創建或刪除節點。
節點父、子和同胞
節點樹中的節點彼此擁有層級關系。
父(parent)、子(child)和同胞(sibling)等術語用于描述這些關系。父節點擁有子節點。同級的子節點被稱為同胞(兄弟或姐妹)。
在節點樹中,頂端節點被稱為根(root)
每個節點都有父節點、除了根(它沒有父節點)
一個節點可擁有任意數量的子
同胞是擁有相同父節點的節點
下面的圖片展示了節點樹的一部分,以及節點之間的關系:
請看下面的 HTML 片段:
<html>
<head>
<title>DOM 教程</title>
</head>
<body>
<h1>DOM 第一課</h1>
<p>Hello world!</p>
</body>
</html>
并且:
并且:
警告!
DOM 處理中的常見錯誤是希望元素節點包含文本。
在本例中:<title>DOM 教程</title>,元素節點 <title>,包含值為 "DOM 教程" 的文本節點。
可通過節點的 innerHTML 屬性來訪問文本節點的值。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。