一、DOM簡介
D——document,沒有文檔,也就是沒有網頁,DOM就無從談起。
當創建了一個網頁並把它加載到web瀏覽器中時,DOM就悄然而生。瀏覽器根據網頁文檔創建一個文檔對象。
O——object,對象。
對象有三種,
1、用戶自定義對象
2、內建對象,javascript中的對象,如Array,Math,Date等。
3、宿主對象,由瀏覽器提供的對象,如window對象。
M——model,模型。
正如一個火車模型代表一列真正的火車,DOM代表被加載到瀏覽器窗口里的當前網頁。瀏覽器為我們提供了當前網頁的模型,可通過javascript去讀寫它。
所以DOM(Document Object Model),文檔對象模型,可以簡單理解為代表網頁文檔的一顆樹(模型)。
二、nodeName、nodeValue以及nodeType和innerHTML和value
DOM將網頁表示為一顆樹,該樹的節點類型有多種。
元素節點——html標簽
文本節點——文本
屬性節點——屬性總是被包含在標簽里,所以屬性節點總是被包含在元素節點當中。(元素節點(屬性節點,文本節點))
通常可以通過開發者工具(如firebug)查看dom結構,但是要說明一點,開發者工具中的DOM並不完整,因為有些元素存在於DOM中,但是不會被開發者工具顯示。比如回車會被當做一個文本節點。
點我查看DOM中的空白符
1、nodeType
DOM本質就是一堆節點的集合,由於包含不同類型的信息,所以就有不同類型的節點。接下來看nodeType。
元素節點,nodeType為1
屬性節點,nodeType為2
文本節點,nodeType為3
文檔節點,nodeType為9
Note:文檔節點並不是根元素(html),因為注釋等內容可以出現在根元素之外。所以在構造DOM樹時,根元素並不適合作為根節點,所以就出現了文檔節點,而根節點作為文檔節點的子節點。
<body> <p id="p">段落</p> <script type="text/javascript"> var element=document.getElementById("p"); var text=document.getElementById("p").firstChild; var property=document.getElementById("p").getAttributeNode("id"); console.log("元素節點nodeType返回值"+element.nodeType); console.log("文本節點nodeType返回值"+text.nodeType); console.log("屬性節點nodeType返回值"+property.nodeType); console.log("文檔節點nodeType返回值"+document.nodeType); </script> </body>
這里我要重點說一下屬性節點。先上一張圖:
圖是w3schools教程中的,屬性節點(紅色框)的畫法是很特別的,我第一次看教程沒看懂為什么要這么畫?難道其中有隱情?但是教程也都沒有提及。
可能很多人沒注意,我現在來說一下。
因為屬性節點實際上是附屬於元素的,所以不被看做是元素的子節點,因為並沒有被當做是DOM的一部分。在屬性節點上調用parentNode,previousSibling和nextSibling都返回null。
<body> <p id="p">段落</p> <script type="text/javascript"> var element=document.getElementById("p"); console.log("<p>的子節點是 "+element.firstChild); var property=document.getElementById("p").getAttributeNode("id"); console.log("屬性節點的parentNode "+property.parentNode); console.log("屬性節點的左鄰節點 "+property.previousSibling); console.log("屬性節點的右鄰節點 "+property.nextSibling); </script> </body>
所以w3schools的畫法也就可以理解了,屬性節點不是子節點,所以“隨便”掛到元素節點上。
nodeType屬性經常和if配合使用,確保不會在錯誤的節點類型上執行錯誤的操作。比如
<script type="text/javascript"> window.onload=function(){ var mynode=document.getElementById("p"); if(mynode.nodeType==1){ mynode.setAttribute('title','段落'); mynode.style.color="red"; } } </script>
補充內容:
值——元素類型
1——元素節點,表示文檔中元素,元素節點是唯一能夠擁有屬性的節點類型。元素和屬性的文本內容都是由文本節點來表示的。
2——屬性節點,代表元素的屬性。
3——文本節點,只包含文本內容,也可以只包含空白。
4——CDATA段節點。
5——ENTITY REFERENCE實體引用節點。實體引用節點可以被用於表示DOM樹中的一個實體引用。
6——ENTITY實體節點,表示文檔中已分析或未分析的實體。
7——PI(processing instruction)處理指令節點,
8——注釋節點,表示注釋的內容。
9——文檔節點(DOCUMENT),文檔樹的根節點。
10——DOCUMENT TYPE文檔類型節點。
11——DOCUMENT FRAGMENT文檔片段節點,文檔片段是"輕量級的"或"最小的"Document對象。
12——NOTATION記號節點表示了在DTD中聲明的記號。
2、nodeName
對於元素節點,nodeName就是標簽名。元素節點也可以通過tagName獲取標簽名。【update 20170315】
對於文本節點,nodeName永遠是#text
對於屬性節點,nodeName是屬性名稱
對於文檔節點,nodeName永遠是#document
注意:
nodeName是一個只讀屬性,不能進行設置。
nodeName所包含的XML元素的標簽名稱永遠是大寫的。
<body> <p id="p">段落</p> <script type="text/javascript"> var element=document.getElementById("p"); var text=document.getElementById("p").firstChild; var property=document.getElementById("p").getAttributeNode("id"); console.log("元素節點nodeName返回值"+element.nodeName);/*元素節點返回標簽名P*/ console.log("文本節點nodeName返回值"+text.nodeName);/*文本節點永遠返回#text*/ console.log("屬性節點nodeName返回值"+property.nodeName);/*返回屬性名,這里是id*/ console.log("文檔節點nodeName返回值"+document.nodeName); </script> </body> </html>
3、nodeValue
對於元素節點,因為本身不直接包含文本,所以nodeValue是不可用的。
對於文本節點,nodeValue值為文本值
對於屬性節點,nodeValue值為屬性值
<body> <p id="p">段落</p> <script type="text/javascript"> var element=document.getElementById("p"); var text=document.getElementById("p").firstChild; var property=document.getElementById("p").getAttributeNode("id"); console.log("元素節點nodeValue返回值"+element.nodeValue); console.log("文本節點nodeValue返回值"+text.nodeValue); console.log("屬性節點nodeValue返回值"+property.nodeValue); console.log("文檔節點nodeValue返回值"+document.nodeValue); </script> </body> </html>
下面是一個關於nodeType,nodeName和nodeValue的綜合demo。

<body> <h1 id="h1">An HTML Document</h1> <p><input id="elementNode" type="button" value="查看元素節點的各項值"></p> <p><input id="text" type="button" value="查看文本節點的各項值"></p> <p><input id="documentNode" type="button" value="查看文檔節點的各項值"></p> <p><input id="property"type="button" alt="這是個演示按鈕" title="演示按鈕提示標題" name="property" value="本按鈕的屬性節點演示" /></p> <script type="text/javascript"> function showElement(){ var element=document.getElementById("h1"); alert('nodetype:'+element.nodeType);//nodeType=1 alert('nodeName:'+element.nodeName); alert('nodeValue:'+element.nodeValue); //null alert('element:'+element); } function showText(){ var element=document.getElementById("h1"); var text=element.childNodes[0]; alert('nodeType:'+text.nodeType); //nodeType=3 alert('nodeValue:'+text.nodeValue); //文本節點的nodeValue是其文本內容 text.nodeValue=text.nodeValue+"abc"; //文本內容添加修改刪除等等。 alert('nodeName:'+text.nodeName); alert(text.data); //data同樣是其內容,這個屬性下同樣可以增刪改。 } function showDocument(){ alert('nodeType:'+document.nodeType); //9 alert('nodeName:'+document.nodeName); alert(document); } function showAttr(){ var property=document.getElementById("property"); //演示按鈕,有很多屬性 var attrs=property.attributes; for(var i=0;i<attrs.length ;i++){ var attr=attrs[i]; alert('nodeType:'+attr.nodeType); //attribute 的nodeType=2 alert('attr:'+attr); alert('attr.name:'+attr.name+'='+attr.value); } } function demo(){ var element=document.getElementById("elementNode"); element.onclick=showElement;//按鈕1獲取節點的nodeType值 var text=document.getElementById("text"); text.onclick=showText; var documentNode=document.getElementById("documentNode"); documentNode.onclick=showDocument; var property=document.getElementById("property"); property.onclick=showAttr; } window.onload=demo; </script> </body>
4、innerHTML
innerHTML只對元素節點有用,獲取元素節點內容,也就是元素節點包含的文本節點的值。其他節點使用nodeValue。
<body> <p id="p">p標簽的內容</p> <script> var p=document.getElementById("p"); console.log(p.innerHTML); console.log(p.firstChild);//p的第一個子節點是文本節點 console.log(p.firstChild.innerHTML);//無效 console.log(p.firstChild.nodeValue);//使用nodeValue進行訪問 </script> </body>
5、value
盡管innerHTML只對元素節點有用,但不是所有的元素節點都能使用innerHTML,比如像<input> 這樣的替換元素。
<label>姓名:<input type="text" value="lxy" /></label> <script> var oinput=document.getElementsByTagName("input")[0]; console.log("oinput.innerHTML "+oinput.innerHTML);//沒有內容 console.log("oinput.value "+oinput.value);//獲取input的value屬性 </script>
其實很好理解,因為input里面不包含文本節點,所以用innerHTML獲取不到文本節點的值。可使用value獲取其屬性值。
類似的,form里的DOM元素(input select checkbox textarea radio)值獲取時都使用value。
textarea雖然可以訪問innerHTML,但是獲取的是初始文檔中的值,當頁面的textarea中的值發生變化時,innerHTML不會及時更新。有興趣可自己寫demo測試。
三、HTML DOM 訪問節點
DOM的思想就是每個節點都是對象,是對象我們就可以通過一些方法獲取它或者改變它的屬性等。
可以通過多種方法來查找DOM元素:
a、使用getElementById()和getElementByTagName()和getElementsByClassName()方法
b、通過一個元素節點的parentNode、childNodes、children、firstChild和lastChild和previousSibling和nextSibling
c、通過document.documentElement和document.body
1、getElementById()和getElementsByTagName()和getElementsByClassName()和getElementsByName()
這四種方法會忽略文檔的結構。
getElementById()不多說。
getElementsByTagName()使用指定標簽名返回所有元素,這些元素是調用該方法的元素的后代。
getElementsByClassName()返回帶有指定類名的所有元素的節點列表。
getElementsByName()根據元素的name屬性返回所有元素的節點列表。(IE容錯能力較強,會得到一個數組,其中包括id等於name值的。我還沒測試)
<body> <span class="class">span標簽內容</span> <p class="class">p標簽的內容</p> <script> var aclass=document.getElementsByClassName("class"); for(var i=0;i<aclass.length;i++){ console.log(aclass[i].innerHTML); } </script> </body>
還有一些方法是和文檔結構相關的,因為DOM樹中的節點是緊密相連的
上——parentNode
下——childNodes/children,firstChild,lastChild
左/右——previousSibling/nextSibling
2、childNodes
childNodes保存子節點的引用,包括空白也在內(除了IE<9),也包括<script>在內。
<!DOCTYPE HTML> <!-- My document --> <html> <head> <title>My Document</title> </head> <body> <h1>Header</h1> <p> Paragraph </p> <script type="text/javascript"> window.onload=function(){ var childNodes=document.body.childNodes; for(var i=0;i<childNodes.length;i++){ console.log(childNodes[i]); } } </script> </body> </html>
在chrome中效果如下,
3、children
如果只想獲得子節點中的元素節點,跳過文本節點,應該使用children屬性。
IE<9會在children屬性中列出注釋節點。
還是上面的例子,將document.body.childNodes改為document.body.children;效果如下:
使用childNodes和children獲得是一個集合,想要獲得單個元素,可以使用一些快速的索引siblings、parent等。
4、firstChild和lastChild
firstChild和lastChild是childNodes中首尾節點的快速索引。
var body=document.body; alert(body.firstChild===body.childNodes[0]);//true alert(body.lastChild===body.childNodes[body.childNodes.length-1]);//true
對firstChild最普通的用法是訪問某個元素節點的文本:
var x=[a paragraph];
var text=x.firstChild.nodeValue;
小技能:
寫代碼檢查DOM節點是否為空,就是說沒有children或者文本。可用以下三種方法”
if (elem.childNodes.length) { ... } if (elem.firstChild) { ... } if (elem.lastChild) { ... }//最快
5、parentNode,previousSibling和nextSibling
獲取父節點或者左右相鄰的節點。
可借助這些屬性來更新DOM,增刪元素。
parentNode屬性常被用來改變文檔的結構。假設希望從文檔中刪除帶有id為"maindiv"的節點:
var x=document.getElementById("maindiv"); x.parentNode.removeChild(x);
首先找到帶有指定id的節點,再移至其父節點並執行removeChidld()方法。
現在有一個問題:
document.body.lastChild.nextSibling總是null嗎?//是
同樣document.body.children[0].previousSibling呢?//不一定,null或者文本。因為document.body.children[0]代表第一個元素節點,可能會有文本節點作為它 previousSibling。
6、特殊入口
訪問DOM還有兩個特別的入口——document.documentElement和document.body。
document.documentElement代表<html>元素。
document.body代表<body>元素,可以為null,比如在body沒有呈現的時候引用就是null。
<head> <script type="text/javascript"> alert("head部分的body"+document.body);/*null*/ </script> </head>
四、HTML DOM 操作
1、創建新元素(節點)
- createElement()//創建一具體的元素
- createTextNode()//創建一個文本節點
- createDocumentFragment()//創建一個DOM片段
createDocumentFragment()創建一個文檔碎片,把所有的節點都加在上面,最后把文檔碎片一次性添加到document中,比一次次修改DOM更高效。
查看createDocumentFragment()執行效率了解更多。
2、元素操作
a、更改元素內容
<body> <p id="p">p標簽的內容</p> <script> var p=document.getElementById("p"); alert("暫停觀察"); p.innerHTML="內容替換了"; </script> </body>
還可以直接給文本節點的nodeValue賦值。
比如:p.firstChild.nodeValue="再次更新文字"
b、新增元素(appendChild和insertBefore)
appendChild()將新元素作為父元素的最后一個子元素。
<body> <p id="p1">p標簽的內容</p> <script> var p1=document.getElementById("p1"); var newP=document.createElement("p"); var text=document.createTextNode("新增的p標簽的內容"); newP.appendChild(text); p1.parentNode.appendChild(newP); </script> </body>
insertBefore()通過父元素調用,將第一個元素插入第二個元素前面
<body> <p id="p1">p標簽的內容</p> <script> var p1=document.getElementById("p1"); var newP=document.createElement("p"); var text=document.createTextNode("新增的p標簽的內容"); newP.appendChild(text); p1.parentNode.insertBefore(newP,p1);//通過父p1的父元素將newP插入到p1的前面 </script> </body>
c、移除元素removeChild
DOM中刪除元素,必須通過父元素進行操作。
<body> <p id="p1">p標簽的內容</p> <script> var p1=document.getElementById("p1"); alert("暫停觀察"); p1.parentNode.removeChild(p1); </script> </body>
d、替換元素replaceChild
替換元素也必須通過父元素來進行,接收2個參數,和insertBefore類似,用第一個參數替換第二個。
<body> <p id="p1">p標簽的內容</p> <script> var p1=document.getElementById("p1"); var p2=document.createElement("p"); var text=document.createTextNode("替換的文本"); p2.appendChild(text); alert("暫停觀察"); p1.parentNode.replaceChild(p2,p1); </script> </body>
可見DOM結構的改動(增刪改)都是通過父節點來進行的。
e、一個綜合demo
通過InnerHTML更改元素內容,通過appendChild新增元素,通過removeChild移除元素。
<body> <input type="text" id="text"/> <input type="button" value="添加li" id="button"> <ul> </ul> <script> window.onload=function(){ var obutton=document.getElementById("button"); obutton.onclick=createLi; } function createLi(){ var oText=document.getElementById("text"); var oUl=document.getElementsByTagName("ul")[0]; var oLi=document.createElement("li"); oLi.innerHTML=oText.value; var oA=document.createElement("a"); oA.innerHTML="刪除"; oA.href="javascript:;"; oA.onclick=function(){ oUl.removeChild(this.parentNode); } oLi.appendChild(oA); oUl.appendChild(oLi); } </script> </body>
3、特性操作
用getAttribute(),setAttribute()和removeAttribute()控制HTML標簽的特性 。
<body> <a href="#">starof</a> <script type="text/javascript"> var a=document.getElementsByTagName("a")[0]; alert(a.getAttribute("href")); a.setAttribute("href","http://www.cnblogs.com/starof"); alert(a.getAttribute("href")); a.removeAttribute("href"); alert(a.getAttribute("href")); </script> </body>
通過setAttribute更改樣式:(就是重置內聯樣式)
不是p1.setAttribute("color","green");
而是p1.setAttribute("style","color:green;background-color:orange;");
因為setAttribute的修改是動態的,所以查看源代碼時看不到!
4、樣式操作
obj.style.屬性=屬性值,是通過添加內聯樣式去覆蓋已有樣式的。
修改文字顏色為紅色
<body> <p id="p">p標簽的內容</p> <script> var p=document.getElementById("p"); alert("暫停觀察"); p.style.color="red"; </script> </body>
5、事件操作
DOM對HTML事件做響應。
事件處理的工作機制:
在元素添加了事件處理函數后,一旦預定事件發生,相應的JavaScript代碼可以返回一個結果,而這個結果將被傳遞回那個事件處理函數。
比如給某個鏈接添加一個onclick事件處理函數,並讓這個處理函數所觸發的JavaScript代碼返回布爾值true或false。這樣一來,當這個鏈接被點擊時,如果那段JavaScript返回給onclick事件處理函數的值是true,onclick事件處理函數將認為“這個鏈接被點擊了”;反之如果那段JavaScript代碼返回給onclick事件處理函數的值是false,onclick事件處理函數將認為“這個函數沒有被點擊”。
可以拿下面代碼驗證:
<a href="http://www.baidu.com" onclick="return false;">click me</a>
關於事件這部分內容太多,有興趣可看
javaScript事件(四)event的公共成員(屬性和方法)