原生DOM入門

吳少666發表於2018-01-08

原生DOM介面挺多的,需要花點時間研究下,不過先把基礎整好,後面框架估計好學點。

1. DOM是啥

1.1 知識回顧

先回顧一下HTML的基本結構

<!DOCTYPE html>
<html lang="zh-Hans">
<head>
</head>
<body>
</body>
</html>
複製程式碼

以上就是最簡單的HTML 5的結構。一般我們會把它處理成一棵樹,一棵節點樹。

樹結構

以上就是一棵樹,瀏覽器把html渲染成的樹,也就是Document結構。每個框就是一個Element、標題等文字內容是Text。


Document、Element、Text 的祖先都是Node。 以下是MDN的繼承樹

原生DOM入門

可是在記憶體中,存的不是html樹,是一棵對應html各個節點的物件樹,而且物件樹的節點是與html樹的節點一一對應的。

物件樹

以上是記憶體中的物件樹。這些物件應該怎麼定義,是由DOM規範規定的。

也可以如下這麼理解

Js基本語法的對應

頁面中的節點,根據Element、Text、Document、Comment這些建構函式,構造出物件來,記憶體就理解了。

比如構造div

var div = document.createElement('div')
undefined
div 
<div></div> //列印結果
複製程式碼

createElement就是建構函式。

把DOM的對應和JS基本語法練習起來。

1.2 DOM的真面目

前面的基礎分析完了,就可以知道什麼是DOM了。

完整的DOM

DOM就是完整的把Document和Object對映到一起,符合DOM規範的結構,所以具備很多的API。

DOM 是 JavaScript 操作網頁的介面,全稱為“文件物件模型”(Document Object Model)。它的作用是將網頁轉為一個 JavaScript 物件,從而可以用指令碼進行各種操作(比如增刪內容)。

瀏覽器會根據 DOM 模型,將結構化文件(比如 HTML 和 XML)解析成一系列的節點,再由這些節點組成一個樹狀結構(DOM Tree)。所有的節點和最終的樹狀結構,都有規範的對外介面。所以,DOM 可以理解成網頁的程式設計介面。DOM 有自己的國際標準,目前的通用版本是DOM 3,下一代版本DOM 4正在擬定中。

嚴格地說,DOM 不屬於 JavaScript,但是操作 DOM 是 JavaScript 最常見的任務,而 JavaScript 也是最常用於 DOM 操作的語言。


規範裡的DOM模型竟然有多達31個介面,我先只挑Node介面和Document介面學習。

原生DOM入門


2. 原生DOM API

DOM是一棵樹,樹上有Node,Node分為Document、Element、Text,其他的可以忽略。

由前面的繼承圖可知Node的研究價值很高,我們先來看Node介面的屬性和方法。 下面是MDN對Node介面的解釋

Node是一個介面,許多DOM型別從這個介面繼承,並允許類似地處理(或測試)這些各種型別。

  • 以下介面都從Node繼承其方法和屬性: Document, Element, CharacterData (which Text, Comment, and CDATASection inherit), ProcessingInstruction, DocumentFragment, DocumentType, Notation, Entity, EntityReference

2.1 Node的屬性

DOM樹的最小單位就是節點(node)。文件的樹形結構就是有各個不同列型別的節點組成的,每個節點都可以看做是這棵DOM樹的葉子

常見的七種node型別

名字 作用
Document 整個文件樹的頂層節點 但不是根節點
DocumentType doctype標籤
Element 網頁的其他各種標籤
Attribute 標籤的屬性
Text 標籤與標籤之間的文字
Comment 註釋
DocumentFragment 文件的片段

我們只關心Document、Element、Text

2.1.1 node之間的關係屬性

  1. 一個最頂層的節點 document,代表整個文件。
  2. 一個根節點 html,是文件裡面最高的一層,是根節點。其他所有的html標籤都是他的下級
  3. 其他節點與周圍節點的關係
    1. parentNode: 直接的那個上級的節點
    2. childNodes: 直接的下一級的節點
    3. sibling:擁有同一個父節點的節點
Node.childNodes

返回一個NodeList集合,成員包括當前節點的所有子節點。注意,除了HTML元素節點,該屬性返回的還包括Text節點和Comment節點。如果當前節點不包括任何子節點,則返回一個空的NodeList集合。由於NodeList物件是一個動態集合,一旦子節點發生變化,立刻會反映在返回結果之中。

  • childNodes

注意: childNodes會把text也列印出來,也就是兩個標籤之間的換行符。

文字節點

而且childNodes返回一個偽陣列。這個偽陣列物件的每一個元素依然都是html元素,如果想操作元素的內容還要用元素的其他屬性。

  • document的childNodes只有兩個
document.childNodes
(2) [<!DOCTYPE html>, html]
	0:<!DOCTYPE html>
	1:html
	length:2
	__proto__: NodeList
複製程式碼
  • 所以我們不想列印出text節點,可以使用children屬性啊
ParentNode.children //ParentNode要是一個HTMLCollection
複製程式碼

上述children屬性會返回 一個Node的子elements 而沒有text節點。

Node.firstChild和Node.lastChild

firstChild屬性返回當前節點的第一個子節點,如果當前節點沒有子節點,則返回null(注意,不是undefined)。

Node.lastChild屬性返回當前節點的最後一個子節點,如果當前節點沒有子節點,則返回null。

Node.nextSibling和Node.previousSibling

Node.nextSibling屬性返回緊跟在當前節點後面的第一個同級節點。如果當前節點後面沒有同級節點,則返回null

previousSibling屬性返回當前節點前面的、距離最近的一個同級節點。如果當前節點前面沒有同級節點,則返回null

以上的三組屬性使用時一定要注意結果會有text的影響。

Node.parentNode
document.parentNode
null
複製程式碼

parentNode屬性返回當前節點的父節點。對於一個節點來說,它的父節點只可能是三種型別:element節點、document節點和documentfragment節點。

而且document節點和documentfragment節點,它們的父節點都是null。另外,對於那些生成後還沒插入DOM樹的節點,父節點也是null。

Node.parentElement

parentElement屬性返回當前節點的父Element節點。如果當前節點沒有父節點,或者父節點型別不是Element節點,則返回null。

注意: 在IE瀏覽器中,只有Element節點才有該屬性,其他瀏覽器則是所有型別的節點都有該屬性。

ownerDocument屬性返回當前節點所在的頂層文件物件,即document物件


2.1.2 node自身的屬性

nodeName和nodeType

nodeName返回node的名字,如果是element那名字是大寫的,其他的名字前面寫上#。nodeType返回node的型別,一般用數字表示,1表示element(也可以用Node.ELEMENT_NODE來表示),3表示text(Node.TEXT_NODE)。

  • 如果是element,那麼nodeName === tagName
  • 如果是text,那麼nodeName = #text, tagName = undefined

nodeName和tagName

關於nodeType有個詳細的表格,應該檢視MDN記住。對照表

nodeValue

nodeValue屬性返回或設定當前節點的值。對於text, comment節點來說, nodeValue返回該節點的文字內容,對於 attribute 節點來說, 返回該屬性的屬性值,而對於document和element節點來說,返回null。 貌似沒個卵用,最常見的element都是null,還是用下面的textContent

textContent和innerText之爭

火狐推出的textContent,獲得一個節點及其後代的文字內容,一般用這個獲得元素的內容。

IE推出的innerText,兩者有很大的區別。

關於textContent、innerText、innnerHTML之間的區別請看MDN


2.2 Node的方法

Node.appendChild()

Node.appendChild() 方法將一個節點新增到指定父節點的子節點列表的末尾

var child = node.appendChild(child);

  • node 是要插入子節點的父節點.
  • child即是引數又是這個方法的返回值.

div新增空的p元素

列印出p那個元素

Node.cloneNode()

這個方法就是克隆一個node,分為淺拷貝和深拷貝。

  • 淺拷貝,Node.cloneNode()只克隆元素節點本身,而不會克隆它的子節點。包括它的文字節點
  • 深拷貝,Node.cloneNode(true)克隆元素的所有屬性以及子節點

原生DOM入門

注意:為了防止一個文件中出現兩個ID重複的元素,使用cloneNode()方法克隆的節點在需要時應該指定另外一個與原ID值不同的ID

原生DOM入門
如果你想克隆同一個id的元素到不同的元素後面,會報錯。

Node.contains()

判斷一個節點是不是另一個節點的子節點。

div.contains(div1)
複製程式碼

div1是div的子節點就返回true。

Node.hasChildNodes()
div.hasChildNodes()
複製程式碼

判斷div節點是否還有子節點,有子節點就返回true。

Node.insertBefore()

在當前節點的某個子節點之前再插入一個子節點。

var insertedElement = parentElement.insertBefore(newElement, referenceElement);
複製程式碼

在parentElement節點的子節點referenceElement前面插入一個newElement節點。

如果referenceElementnullnewElement將被插入到子節點的末尾*。*如果newElement已經在DOM樹中,newElement首先會從DOM樹中移除。

沒有 insertAfter 方法。可以使用 insertBefore 方法和 nextSibling 來模擬它。

parentDiv.insertBefore(sp1, sp2.nextSibling);
複製程式碼

只要sp2.nextSibling === null,那麼就可以在parentDiv的末尾新增sp1元素。

Node.isEqualNode()和Node.isSameNode()

兩者都是比較兩個node是否相等。不過isEqualNode()是兩個node看起來相等就返回true,isSameNode()嚴格使用===判斷,而且該方法已被廢棄,如果要嚴格判斷兩個node是否指向同一個物件,直接用node1 === node2。

Node.normalize()

就是規範化的意思。什麼是規範化,在一個"規範化"後的DOM樹中,不存在一個空的文字節點,或者兩個相鄰的文字節點。

var wrapper = document.createElement("div");

wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));

// 這時(規範化之前),wrapper.childNodes.length === 2
// wrapper.childNodes[0].textContent === "Part 1 "
// wrapper.childNodes[1].textContent === "Part 2 "

wrapper.normalize();
// 現在(規範化之後), wrapper.childNodes.length === 1
// wrapper.childNodes[0].textContent === "Part 1 Part 2"
複製程式碼

以上是MDN的例子,很好懂。

未規範化的時候

規範化後

以上是自己模仿的demo

Node.removeChild()和Node.replaceChild()

Node.removeChild()是從當前節點刪除一個子節點,不過記憶體裡面依然存在,只不過不在頁面顯示了,返回的就是被移除的那個節點。所以說一個節點移除以後,依然可以使用它,比如插入到另一個節點下面。

未移除子元素的時候

移除之後

以上是我做的demo

Node.replaceChild方法用於將一個新的節點,替換掉當前節點的一個子節點。它接受兩個引數,第一個引數是用來替換的新節點,第二個引數將要被替換走的子節點。它返回被替換走的那個節點。

未替換

替換了

我做的實驗demo

Document屬性和方法

關於Document介面的屬性和方法,且聽下回分解~

相關文章