在上篇文章這DOM(一)——簡介中我們介紹了Node的所有節點類型,這篇文章我將針對其中比較重要的節點進行介紹。
Node類型
nodeType和nodeName
每一個節點都有一個nodeType
和nodeName
屬性,用於表明節點的類型。節點類型共有12種。
ELEMENT_NODE =1
ATTRIBUTE_NODE =2
TEXT_NODE =3
CDATA_SECTION_NODE =4
ENTITY_REFERENCE_NODE =5
ENTITY_NODE =6
PROCESSING_INSTRUCTION_NODE =7
COMMENT_NODE =8
DOCUMENT_NODE =9
DOCUMENT_TYPE_NODE =10
DOCUMENT_FRAGMENT_NODE =11
NOTATION_NODE =12
nodeType
和nodeName
只是同一節點類型的不同方式表達。
nodeType
是用數字表示節點類型,nodeName
是用字符串表示節點類型。實際上他們映射的是同一個節點。
nodeValue
絕大數節點類型(除了Text(文本節點)和Comment(註釋節點))的nodeValue屬性都返回null。這個節點屬性就是獲取Text與Comment節點實際文本字符串。
其他節點屬性
除了上述的三個節點屬性之外,還有一些很重要的節點屬性。
- childNodes
- firstChild
- lastChild
- nextSibling
- parentNode
- previousSibling
單純地介紹太過於抽象,來一張圖,或許大家就明白了這些節點屬性的作用。
childNodes屬性可以用來獲取任何一個元素的所有子元素,它是一個包含這個元素全部子元素的數組。
其他的屬性,大家看圖基本上就知道意思了,所以不多說了。
操縱節點
(常用的)操縱節點的方法有:
appendChild()
在節點內,添加最後一個子節點。(有點類似於僞元素after,是在元素內部最後添加的)
cloneNode()
創建節點的拷貝,並返回該副本。該方法會克隆所有屬性以及它們的值。
該方法不會複製DOM節點中的JavaScript屬性,但是IE會有bug——它會複製事件處理程序。removeChild()
刪除父節點的一個子節點
replaceChild()
用新節點替換某個子節點。這個新節點可以是文檔中某個已存在的節點,也可創建新的節點。
insertBefore()
在已有的子節點前插入一個新的子節點。
可以說掌握了上述5中節點的操作方法,基本上就熟悉DOM的節點操作了。
Document類型
Document類型表示文檔。在瀏覽器中,document對象是HTMLDocument(繼承自Document)的一個實例。
常見的節點接口還包括:
// '<' 表示'從左側繼承'
Object < Node < Element < HTMLElement
Object < Node < Attr(DOM4中棄用)
Object < Node < CharacterData < Text
Object < Node < Document < HTMLDocument
Object < Node < DocumentFragment
文檔信息
document對象常用的屬性有四個,分別是URL、domain、referrer和cookie。由於前三個屬性與網址都有關係,所以先簡單介紹一下網址的組成。
以百度首頁爲例子:
https://www.baidu.com/
URL格式:
<協議>://<主機>:[端口號]<路徑>
協議:指定使用的傳輸協議
主機和端口號:存放資源的主機的域名。端口號可選,省略是使用默認端口。
路徑:主機上的一個目錄或文件的地址
協議:https
服務器:www
域名:www.baidu.com
如果你想進一步瞭解,這篇文章會幫你——《詳解URL的組成》
- URL屬性
URL——統一資源定位符。該屬性可以顯示當前頁面完整的URL(既地址欄中顯示的URL)
在控制檯中輸入下列代碼,可以獲取當前頁面的完整URL
console.log(document.URL);
referrer屬性
referrer屬性保存着鏈接到當前頁面的那個頁面的URL。該屬性是計算流量來源的方法之一,更多相關信息請瀏覽張鑫旭老師的文章《JS獲取上一訪問頁面URL地址document.referrer實踐》domain屬性
domain包含了當前頁面的域名。
在要介紹的前三個屬性裏面,只有domain能夠進行設置。但是由於安全方面的原因,domain設置的值有限制。如果URL中包含一個子域名,例如www.baidu.com,那麼只能將domain設置爲”baidu.com”,而不能設置成jingyan.baidu.com。換言之,就是domain只能做“減法”,不能做加法或者完全更改其內容(例如改成csdn.net)。
domain最大的作用是突破了跨域安全限制。由於跨域安全的限制,來自不同子域的頁面無法通過JavaScript通信的。當在頁面包含內嵌框架(iframe)或其他子域的時,設置將頁面和內部的框架都設置成同一個domain,就可以由同一個js文件進行控制了。
瀏覽器對domain屬性還有一個限制:domain屬性只能從緊繃(tight)到鬆散(loose)。即只能從子域名變成主域名,而不能從主域名到子域名。
//主域名 baidu.com
//子域名1 www.baidu.com
//子域名2 jingyan.baidu.com
//當前頁面 jingyan.baidu.com
document.domain="baidu.com";//成功
//當前頁面 www.baidu.com
document.domain="baidu.com";//成功
document.domain="www.baidu.com";//失敗
- cookie屬性
cookie屬性可以使一些數據存儲到本地電腦上,這樣下次再打開指定網頁的時候就可以根據記錄的信息完成相關操作了(如自動登錄)。
cookie的屬性操作非常簡單,只要是做到“鍵=值”就可以。
更多信息cookie相關操作,你可以參考《JavaScript Cookie》
文檔方法
最常見的文檔方法分成兩類,一類是查找元素,另一類是文檔寫入。
查找元素
常見的查找元素的方法有:
document.getElementById()
返回對擁有指定 id 的第一個對象的引用。id按照嚴格匹配,區分大小寫。
如果頁面中存在多個同名id的元素,則只返回第一次出現的元素。雖然所有瀏覽器都支持該方法,但是它仍有一些兼容性問題:
①在IE8及低版本中,將不會區分id的大小寫。所以id的命名儘量不要以大小寫區分,可能導致兼容性問題。
②在IE7以及更低版本中,會有一個怪異的行爲:如果表單元素的name等於指定id,而且該元素在文檔中帶有給定id的元素前面,那麼IE就會返回那個表單元素。
下面這個例子,低版本IE將會獲取input元素而非div元素。
<input type="text" name="myElement" vaule="Text field">
<div id="myElement">A div</div>
爲了避免出現這種情況,name請不要跟id取相同的字段。
document.getElementsByName()
返回帶有指定名稱的對象的集合。這個方法大家並不常用,兼容性也有待考究。使用的時候需要注意的有兩點:
①返回的是元素的數組,而不是一個元素。
②在IE8及以下版本,只有擁有name屬性的元素才能被獲取,其他元素(非文本標籤元素)是獲取不到的。
下面這個例子,低版本IE將無法獲取指定元素的。
<div name="f"></div>
document.getElementsByTagName()
返回一個包括所有給定標籤名稱的元素集合。該方法會返回元素的順序是它們在文檔中的順序。如果把特殊字符串
"*"
作爲參數傳給該方法,將獲取所有的元素。
注意:
由於IE會將註釋(comment)實現爲元素,因此當調用getElementsByTagName("*")
將會返回所有註釋節點。
文檔寫入
文檔寫入的功能在初級開發中大家見得比較多,其中document.write()
應該是大家最熟悉的,document.writeln()
與前者在功能上也無太大差別。
document.write()
write()
方法可向文檔寫入 HTML 表達式或 JavaScript 代碼。document.writeln()
writeln()
方法可向文檔寫入 HTML 表達式或 JavaScript 代碼,同時會在每個表達式後寫一個換行符。
Element類型
element類型應該是我們最常見的類型了。它的常見子節點包括:
- Element(HTML元素)
- Text(文本節點)
- Comment(註釋節點)
獲取節點之後,既可以使用nodeName,也可以使用tagName屬性來獲取元素的標籤名。這兩個屬性會返回相同的值。
注意:
tagName和nodeName屬性返回的值全都是大寫字母。在XML(XHTML)中標籤名會與源代碼一直,所以比較時最好統一轉換爲大寫。
標準特性
標準特性是每個HTML元素都有的屬性,包括:
- id。元素唯一標識符。
- title。標題。
- lang。元素內容的語言代碼。很少使用。
- dir。語言方向。(例如文字的閱讀方向從左到右,還是從右到左)
- className。就是平時所說的class屬性。(由於class是ECMA的保留字,所以這裏叫className)
操作特性
操作特性分成兩步,一是獲取特性,二是設置特性。
1.獲取特性
- 通過DOM元素本身的屬性來獲取元素特性。不過這種方式存在兼容性問題。
例如:非公認特性在Safari、Opera、Chrome、Firefox瀏覽器中是不存在的,但是IE卻會爲自定義特性也創建屬性。
<div id="myid" data-mydata="mydata" my_special_attribute="hello!"></div>
<script>
document.writeln(document.getElementById("myid").id);//所有瀏覽器都返回myid
document.writeln(document.getElementById("myid").my_special_attribute);//IE返回hello,其他瀏覽器返回undefined
</script>
getAttribute()
方法可以獲取元素的指定特性。這個特性可以是公認的特性,也可以是自定義的特性。
例如下面這個例子:
id
就是公認特性,data-mydata
是html5
新增的自定義特性,my_special_attribute
則是完全自定義特性。這些特性通過getAttribute
均可獲得目標值。
<div id="myid" data-mydata="mydata" my_special_attribute="hello!"></div>
雖然該方法確實很好用,但是在獲取style
和事件處理程序名(如onclick
)上有兼容性問題,特別是在IE瀏覽器中,不同版本之間存在不小的差異。所以在使用該屬性時,不建議用在獲取style
特性和事件處理程序名上。
2.設置特性
- 創建或者修改元素特性。
通過DOM自身屬性進行特性的設置和修改。但是這種方法不支持自定義特性(屬性)的設置。
<div id="myid"></div>
<script>
document.getElementById("myid").className("myclass");//設置成功
document.getElementById("myid").mydata("mydata");//設置失敗(僅在IE中會設置成功)
</script>
setAttribute()
方法也可以創建或改變某個新屬性。這個方法接受兩個參數:①要設置的特性名②要設置的特性值。
如果特性已存在則替換爲新的值,如果不存在則創建一個新的特性並設置它的值。
<div id="myid"></div>
<script>
document.getElementById("myid").setAttribute("class","myclass");
</script>
注意:
①該方法設置的特性名會被統一轉換成小寫形式。
②IE7及以前版本存在bug,通過這個方法設置class
和style
特性就沒有任何效果。
- 移除元素的特性
removeAttribute()
方法接受一個參數——要刪除的特性名字。該方法不僅會清除特性的值,還會把特性從元素中刪除。
注意: IE6及以下版本不支持該方法。
attributes屬性
Element類型是使用attributes
屬性的唯一一個DOM節點類型。
attributes
屬性返回指定節點屬性的集合。
這個屬性在平時中並不常用(對我個人而言),除非是用於遍歷屬性。同時這個屬性在IE7及更早版本中,會返回HTML元素中所有可能的特性(可以說是bug),包括沒有指定的特性。所以本文就不深入研究該屬性了。
創建元素
document對象中有一個方法可以創建元素,那就是createELement()
。
document.createElement()
只接受一個參數——即要創建的元素的標籤名。這個標籤名不區分大小寫。
你可以像下面的代碼一樣創建元素
var p=document.createElment("p");
此時你僅僅是創建了元素,但是這個元素並不是DOM節點樹的組成部分,它只是一個遊蕩在JavaScript世界裏面的一個幽靈。這種情況我們稱之爲文檔碎片(document fragment)。藉助節點方法,如appendChild()
、insertBerfore()
、repalceChild()
可以將文檔碎片添加進DOM節點樹中。這樣我們就可以在瀏覽器窗口中看到這個元素了。
在IE中還有一種document.createElement()
的使用方法,那就是傳入完整的元素標籤。
例如:
var div=document.createElement("<div id=\"newDiv\" class=\"myClass\"></div>")
這種方法有助於避開IE7及跟早版本中動態創建元素的某些問題,包括:
- 不能設置動態創建的
<iframe>
元素的name特性 - 不能通過表單的reset方法(HTML DOM的方法)重置動態創建的
<input>
元素 - 動態創建的
type
特性值爲“reset”的<button>
元素重設不了表單 - 動態創建的一批
name
相同的單選按鈕彼此之間毫無關係。name
值相同的一組單選按鈕本來應該用於表示同一選項的不同值,但是動態創建的一批這種單選按鈕之間卻沒有這種關係。
元素的子節點
元素的childNodes屬性包含了它所有的節點,它返回的是一個存放節點的數組。
對於下面的代碼,IE和其他瀏覽器有不同的解釋:
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
IE認爲<ul>
有三個子節點,分別是三個<li>
節點;
其他瀏覽器認爲有<ul>
有七個子節點,分別是三個<li>
節點和四個文本節點(<li>
之間存在的空白符)。
爲了兼容不同瀏覽器之間的差異,我們建議進行節點屬性的檢查,來保證所選取的節點的正確性。
例如:
for(var i=0,len=element.childNodes.length;i<len;i++){
if(element.childNodes[i].nodeType==1){//進行節點檢測
//do something
}
}
配合上getElementByTagName()
方法可以做到只遍歷直接子節點
var ul=document.getElementById("list"),
items=ul.getElementByTagName("li");
for(var i=0,len=items.childNodes.length;i<len;i++){
if(items.childNodes[i].nodeType==1){//進行直接子節點進行檢測
//do something
}
}
Text類型
文本節點由Text類型表示。Text節點具有以下特徵:
- nodeType的值爲3
- nodeName的值爲
#text
- nodeValue的值爲節點包含的文本內容,該內容可以包含轉義後的HTML字符。但不能包含HTML代碼。
- 沒有子節點
- 父節點(parentNode)是Element節點
創建文本節點
document.createTextNode()
方法可以創建新文本節點,這個方法接受一個參數——要插入節點中的文本。該方法不會轉義其參數內容。
<p id="demo">單擊按鈕</p>
<button onclick="myFunction()">點我</button>
<script>
function myFunction(){
var h=document.createElement("H1");
var t=document.createTextNode("<p>ha ha ha</p>");//內容不會被轉義
h.appendChild(t);
document.body.appendChild(h);
};
</script>
獲取文本節點值與操作文本節點值
獲取文本節點值的方法有兩種,一種是data
,另一種是nodeValue
。
能夠使用data
和nodeValue
這兩種方法獲取文本節點值,是因爲Text
節點繼承了CharacterData
和Node
的所有屬性和方法。
使用方法如下:
<p id="demo">文本節點<p>
<script>
var p=document.getElementById("demo").firstChild;
console.log(p.data);//文本節點
console.log(p.nodeValue);//文本節點
</script>
Node
對象或許大家都熟悉了,但對於CharacterData
對象或許還有一些陌生。
- CharacterData
該接口是一個抽象接口(abstract interface),提供了Text
和Comment
節點的常用功能。正是這個接口爲我們提供了操作文本的相關方法。
兼容性
Chrome | IE | Firefox | Opera | Safari | W3C |
---|---|---|---|---|---|
1.0 | 6 | 1.0 (1.7 or earlier) | Yes | Yes | Yes |
對象屬性
屬性 | 描述 |
---|---|
data |
一個 DOMString(由UTF-16組成的String類型),表示該對象中包含的文本數據。 |
length |
該節點包含的字符數。(只讀) |
常用方法
方法 | 描述 |
---|---|
appendData() |
爲 CharacterData.data 字符串追加指定的 DOMString ;當方法返回時,data 包含的是已合併的 DOMString . |
deleteData() |
在 CharacterData.data 字符串中,從指定位置開始,刪除指定數量的字符;當方法返回時,data 包含的是縮短了的 DOMString . |
insertData() |
在 CharacterData.data 字符串中,在指定的位置,插入指定的字符;當方法返回時,data 包含的是已修改的 DOMString . |
replaceData() |
在 CharacterData.data 字符串中,從指定位置開始,把指定數量的字符替換爲指定的 DOMString ; 當方法返回時, data 包含的是已修改的 DOMString . |
substringData() |
返回一個包含了從 CharacterData.data 中的指定位置開始,指定長度的 DOMString . |
操作演示
<p id="demo">文本節點<p>
<script>
var p=document.getElementById("demo").firstChild;
p.appendData(",尾部新加的文本");
console.log(p.data);//文本節點,尾部新加的文本
p.deleteData(0,5);
console.log(p.data);//尾部新加的文本
p.insertData(0,"頭部插入新文本,");
console.log(p.data);//頭部插入新文本,尾部新加的文本
p.replaceData(0,p.length,"全部更新爲新文本");
console.log(p.data);//全部更新爲新文本
console.log(p.substringData(0,4));//全部更新
</script>
合併與分割文本節點
如果DOM中存在相鄰的同胞文本節點,很容易導致分不清哪兒文本節點表示哪個字符串。
來看下面這段代碼:
<p id="demo"></p>
<script>
var p = document.getElementById("demo"),
price1=document.createTextNode("大甩賣!大甩賣!不要100!"),
price2=document.createTextNode("只要9.99!只要9.99!");
p.appendChild(price1);
p.appendChild(price2);
console.log(p.childNodes.length)//2
</script>
上述代碼在視覺效果上跟下面代碼的效果是一致的:
<p id="demo">大甩賣!大甩賣!不要100!只要9.99!只要9.99!</p>
<script>
var p = document.getElementById("demo");
console.log(p.childNodes.length)//1
</script>
這兩段代碼有一處非常關鍵的地方不同,那就是p.childNodes.length
。前者是2,後者是1。原因很簡單——前者是創建了兩個文本節點然後插入到p
元素中的。但是實際上我們只需要一個文本節點就能完成這個需求。同時如果我們要修改文本節點,很容易出現混亂:我們獲取的節點是不是想要的文本節點?
合併文本節點
爲了規範化文本節點,DOM提供normalize()
這個方法。
normalize()
可以移除空的文本節點,並連接相鄰的文本節點。
合併相鄰的同胞文本節點:
<p id="demo"></p>
<script>
var p = document.getElementById("demo"),
price1=document.createTextNode("大甩賣!大甩賣!不要100!"),
price2=document.createTextNode("只要9.99!只要9.99!");
p.appendChild(price1);
p.appendChild(price2);
p.normalize();//合併兄弟文本節點
console.log(p.childNodes.length)//1
</script>
這樣當我們需要修改文本節點時,就無需顧慮自己獲取的文本節點是不是完整的文本節點了。
分割文本節點
splitText()
方法跟normalize()
方法的作用完全相反,它會將一個文本節點分割爲兩個文本節點,即按照指定的位置分割nodeValue
值。
該方法接受一個參數:要切割的字符串長度。切割的位置從0開始計算,到指定位置位置,分成兩個兄弟文本節點。
<p id="demo">大甩賣!不要100!只要9.99!</p>
<script>
var p = document.getElementById("demo");
console.log(p.childNodes.length);//1
console.log(p.firstChild.data);//大甩賣!不要100!只要9.99!
p.firstChild.splitText(10);//進行切割
console.log(p.childNodes.length);//2
console.log(p.firstChild.data);//大甩賣!不要100!
console.log(p.lastChild.nodeValue);//只要9.99!
</script>
其他節點類型
其他節點類型包括Comment
類型、CDATASection
類型、DocumentType
類型、DocumentFragment
類型,以及Attr
類型。這些節點絕大多數都不常用,放在一起講,瞭解個大概就好了。
Comment類型
Comment
類型是用於表示註釋節點,其nodeName
爲#comment
,nodeType
爲8
。除此之外,它跟Text
節點並無其他不同,兩者共用一套方法。
CDATASection類型
CDATASection
類型只針對XML文檔,表示的是CDATA區域。
DocumentType類型
DocumentType
類型包含着與文檔的doctype有關的所有信息。這是在web瀏覽器中並不常用,且瀏覽器對它的兼容性不好,所以在此不進行深入瞭解。
DocumentFragment類型
在所有節點類型中,只有DocumentFragment
在文檔中沒有對應的標記。DOM規定文檔碎片(document fragment)是一種輕量級的文檔,可以包含和控制節點,但不會像完整的文檔那樣佔用額外的資源。
這是一個相對常用的節點類型,DocumentFragment
節點具有以下特徵:
nodeType
的值爲11
nodeName
的值爲#document-Fragment
nodeValue
的值爲null
parentNode
的值爲null
- 子節點可以是
Element
、Comment
、Text
、CDATASection
等等
DocumentFragment
節點最大的作用就是作爲“倉庫”來使用。由於文檔碎片繼承了Node的所有方法,所以我們可以執行那些針對文檔的DOM操作之後再添加到DOM節點樹中,這樣做將會帶給我們以下這些好處:
①提高性能。減少DOM變換導致的迴流(reflow)和重繪(repaint)。
②不會出錯。文檔碎片添加進DOM節點樹中,只會將文檔碎片的所有子節點添加到相應位置,文檔碎片永遠不會成爲DOM節點樹的一部分。這就避免了我們獲取到錯誤的節點類型。
Attr類型
元素的特性在DOM中以Attr類型來表示。但實際上在DOM4中,已經該節點類型明確廢棄,被合併到Element
節點的attribute
屬性中。如果需要操作元素的特性,我們更加推薦使用getAttribute()
、setAttribute()
等相關方法(請參考上文的Element
節點的attribute
屬性的操作方法)。
參考資料:
JavaScript DOM編程藝術(第2版)
JavaScript 高級程序設計(第3版)
DOM啓蒙
DOM
DOM4-ELEMENT-ATTR
CharacterData
DOMString
CharacterData
Document