DOM系列基礎知識
DOM (Document Object Model) 即文檔對象模型, 針對 HTML 和 XML 文檔的 API (應用程序接口) 。DOM 描繪了一個層次化的節點樹,運行開發人員添加、移除和修改頁面的某一部分。DOM 產生於 網景公司及微軟公司創始的 DHTML(動態 HTML) ,但現在它已經成為表現和操作頁面標記的真正跨平臺、語言中立的方式。
DOM 中的三個字母:
D(文檔)可以理解為整個 Web 加載的網頁文檔;
O(對象)可以理解為類似 window 對象之類的東西,可以調用屬性和方法,這裏我們說的是 document對象;
M(模型)可以理解為網頁文檔的樹型結構。
DOM 有三個等級,分別是 DOM1、DOM2、DOM3。
PS:IE 中的所有 DOM 對象都是以 COM 對象的形式實現的,這意味著 IE 中的 DOM
可能會和其他瀏覽器有一定的差異。
一.DOM介紹
1.節點
加載 HTML 頁面時, Web 瀏覽器生成一個樹型結構, 用來表示頁面內部結構。 DOM 將這種樹型結構理解為由節點組成。
圖1
從上圖的樹型結構,我們理解幾個概念,html 標簽沒有父輩,沒有兄弟,所以 html 標簽為根標簽。head 標簽是 html 子標簽,meta 和 title 標簽之間是兄弟關系。如果把每個標簽當作一個節點的話,那麽這些節點組合成了一棵節點樹。
PS:後面我們經常把標簽稱作為元素,是一個意思。
2.節點種類:元素節點,屬性節點,文本節點
例如:<div title="標簽屬性">測試 Div</div>
圖2
二.查找元素
W3C 提供了比較方便簡單的定位節點的方法和屬性, 以便我們快速的對節點進行操作。
分別為:
圖3
1.getElementById()方法
getElementById()方法,接受一個參數:獲取元素的 ID。如果找到相應的元素則返回該元素的 HTMLDivElement 對象,如果不存在,則返回 null。
document.getElementById(‘box‘); //獲取 id 為 box 的元素節點
註意:某些低版本的瀏覽器會無法識別 getElementById()方法,比如 IE5.0-,這時需要做一些判斷,可以結合上章的瀏覽器檢測來操作。
if (document.getElementById) { //判斷是否支持 getElementById
alert(‘當前瀏覽器支持 getElementById‘);
}
PS:一個程序要想寫的健壯,需要很多的判斷語句,方方面面考慮周到!
當我們通過 getElementById()獲取到特定元素節點時, 這個節點對象就被我們獲取到了,而通過這個節點對象,我們可以訪問它的一系列屬性。
圖4
例如:<div id=”box” title="標簽屬性">測試 Div</div>
document.getElementById(‘box‘).tagName; //輸出:DIV
document.getElementById(‘box‘).innerHTML; //輸出:測試 Div
PS:innerHTML獲取這個元素節點裏的文本(包含HTML標簽),純文本不能包含標簽,innerHTML獲取的是這個元素的文本內容,而不是文本節點。
還可以賦值:
var box = document.getElementById(‘box‘);
box.innerHTML = ‘玩轉<strong>JS</strong>‘; //賦值
註意:上述賦值語句中,HTML標簽會被頁面解析,所以在頁面顯示的時候“JS”會加粗顯示。
圖5
例如:<div id="box" title="標題" class="pox" style="color:red;" bbb="aaa">測試Div</div>
var box = document.getElementById(‘box‘);
alert(box.id); //獲取這個元素節點id屬性的值,註意不是屬性節點alert(box.title); //獲取title屬性的值
alert(box.style); //獲取style屬性對象
alert(box.style.color); //獲取style屬性對象中color屬性的值
alert(box.class); //貌似是保留字
alert(box.className); //獲取class屬性的值
以上都是HTML屬性的直接調用,當然後面還有幾種方式可以調用。
alert(box.bbb); //自定義屬性,直接獲取,非IE不支持
PS:如果有瀏覽器不支持,那麽必須做兼容操作,或者盡可能不去使用。
同樣:
也可以通過上述屬性賦值:
document.getElementById(‘box‘).style.color = ‘red‘; //設置 style 對象中 color 的值
document.getElementById(‘box‘).className = ‘box‘; //設置 class
2.getElementsByTagName()方法
getElementsByTagName()方法將返回一個對象數組 HTMLCollection(NodeList),這個數組保存著所有相同元素名的節點列表。
document.getElementsByTagName(‘*‘); //獲取所有元素
PS:IE 瀏覽器在使用通配符的時候,會把文檔最開始的 html 的規範聲明當作第一個元素節點,所以在求length的時候,IE會比谷歌火狐多一個。代碼示例:
window.onload = function () {
var all = document.getElementsByTagName("*");
alert(all.length);
alert(all[0].tagName); //谷歌火狐返回HTML,IE返回!
};
HTML部分代碼:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
JS部分代碼:
document.getElementsByTagName(‘li‘); //獲取所有 li 元素,返回數組
document.getElementsByTagName(‘li‘)[0]; //獲取第一個 li 元素,HTMLLIElement
document.getElementsByTagName(‘li‘).item(0) //獲取第一個 li 元素,HTMLLIElement
document.getElementsByTagName(‘li‘).length; //獲取所有 li 元素的數目
獲取body節點對象:
var body = document.getElementsByTagName(‘body‘)[0];
alert(body); //返回 HTMLBodyElement對象,body節點
3.getElementsByName()方法
getElementsByName()方法可以獲取相同名稱(name)的元素,返回一個對象數組HTMLCollection(NodeList)。
document.getElementsByName(‘add‘) //獲取 input 元素
document.getElementsByName(‘add‘)[0].value //獲取 input 元素的 value 值
document.getElementsByName(‘add‘)[0].checked //獲取 input 元素的 checked 值
PS:對於並不是 HTML 合法的屬性,那麽在 JS 獲取的兼容性上也會存在差異,IE 瀏覽器支持本身合法的 name 屬性,而不合法的就會出現不兼容的問題,例如,name 屬性本身不是div裏的屬性,所以IE就忽略掉。
4.getAttribute()方法
getAttribute()方法將獲取元素中某個屬性的值。 它和直接使用.屬性獲取屬性值的方法有一定區別。
document.getElementById(‘box‘).getAttribute(‘id‘); //獲取元素的 id 值
document.getElementById(‘box‘).id; //獲取元素的 id 值
document.getElementById(‘box‘).getAttribute(‘mydiv‘); //獲取元素的自定義屬性值
上述獲取自定義屬性值的方法各瀏覽器都支持,區別於.屬性的方法,代碼如下:
document.getElementById(‘box‘).mydiv //獲取元素的自定義屬性值, 非IE不支持
document.getElementById(‘box‘).getAttribute(‘class‘); //獲取元素的 class 值,IE 不支持
document.getElementById(‘box‘).getAttribute(‘className‘); //非 IE 不支持
//跨瀏覽器獲取className(註:不過貌似高版本的都修復了這個問題了!!!)
1 if (box.getAttribute(‘className‘) == null) { 2 3 alert(box.getAttribute(‘class‘)); 4 5 } else { 6 7 alert(box.getAttribute(‘className‘)); 8 9 }
PS: HTML 通用屬性 style 和 onclick, IE7 更低的版本 style 返回一個對象, onclick 返回一個函數式。雖然 IE8 已經修復這個 bug,但為了更好的兼容,開發人員只有盡可能避免使用 getAttribute()訪問 HTML 屬性了,或者碰到特殊的屬性獲取做特殊的兼容處理。
5.setAttribute()方法
setAttribute()方法將設置元素中某個屬性和值。它需要接受兩個參數:屬性名和值。如果屬性本身已存在,那麽就會被覆蓋。
document.getElementById(‘box‘).setAttribute(‘align‘,‘center‘); //設置屬性和值
document.getElementById(‘box‘).setAttribute(‘bbb‘,‘ccc‘); //設置自定義的屬性和值
PS:在 IE7 及更低的版本中,使用 setAttribute()方法設置 class 和 style 屬性是沒有效果的,雖然 IE8 解決了這個 bug,但還是不建議使用。
6.removeAttribute()方法
removeAttribute()可以移除 HTML 屬性。
document.getElementById(‘box‘).removeAttribute(‘style‘); //移除屬性
PS:IE6 及更低版本不支持 removeAttribute()方法。
三.DOM 節點
1.node節點屬性
節點可以分為元素節點、屬性節點和文本節點,而這些節點又有三個非常有用的屬性,分別為:nodeName、nodeType 和 nodeValue。
圖6
示例:<div id="box">測試Div<em>傾斜</em>結尾</div>
var box = document.getElementById(‘box‘);
//alert(box);
//alert(box.nodeName); //獲取元素節點的標簽名,和tagName等價
//alert(box.nodeType); //獲取元素節點的類型值,1
alert(box.nodeValue); //元素節點本身沒有內容,null
註意:node本身把節點指針放在元素<div></div>上,也就是說,本身是沒有value;如果要輸出<div>xxx</div>中裏面包含的文本內容,那麽用前面的innerHTML(innerHTML只能用於元素節點);
alert(box.innerHTML); //獲取元素節點裏面的文本內容
PS:“測試Div<em>傾斜</em>結尾”,如果當作元素節點的文本內容(文本節點不等於文本內容),他就是一個整體。node只能獲取當前節點的東西。
2.層次節點屬性
節點的層次結構可以劃分為: 父節點與子節點、 兄弟節點這兩種。 當我們獲取其中一個
元素節點的時候,就可以使用層次節點屬性來獲取它相關層次的節點。
圖7
1).childNodes 屬性
childeNodes 屬性可以獲取某一個元素節點的所有子節點,這些子節點包含元素子節點和文本子節點。
示例:<div id="box">測試Div<em>傾斜</em>結尾</div>
var box = document.getElementById(‘box‘); //獲取一個元素節點
alert(box.childNodes.length); //3,獲取這個元素節點的所有子節點
alert(box.childNodes[0]); //獲取第一個子節點對象
alert(box.childNodes[0].nodeValue); ////獲取第一個子節點對象內容
PS:使用 childNodes[n]返回子節點對象的時候,有可能返回的是元素子節點,比如HTMLElement;也有可能返回的是文本子節點,比如 Text。元素子節點可以使用 nodeName或者 tagName 獲取標簽名稱,而文本子節點可以使用 nodeValue 獲取。
//示例:通過判斷節點類型,來獲取不同的輸出
1 for (var i = 0; i < box.childNodes.length; i ++) { 2 3 if (box.childNodes[i].nodeType === 1) { 4 5 alert(‘元素節點:‘ + box.childNodes[i].nodeName); 6 7 } else if (box.childNodes[i].nodeType === 3) { 8 9 alert(‘文本節點:‘ + box.childNodes[i].nodeValue); 10 11 } 12 13 }
innerHTML& nodeValue區別:
PS:在獲取到文本節點的時候,是無法使用 innerHTML 這個屬性輸出文本內容的。這個非標準的屬性必須在獲取元素節點的時候,才能輸出裏面包含的文本(上面提到過了)。
PS: innerHTML 和 nodeValue 第一個區別, 就是取值的。 那麽第二個區別就是賦值的時候, nodeValue 會把包含在文本裏的 HTML 轉義成特殊字符, 從而達到形成單純文本的效果。
box.childNodes[0].nodeValue = ‘<strong>abc</strong>‘;
//結果為:<strong>abc</strong>(純文本,不會有加粗效果),且作用在文本節點上。
box.innerHTML = ‘<strong>abc</strong>‘;
//結果為:abc(頁面中加粗顯示),且作用在元素節點上。
2).firstChild 和 lastChild 屬性
firstChild 用於獲取當前元素節點的第一個子節點, 相當於 childNodes[0]; lastChild 用於獲取當前元素節點的最後一個子節點,相當於 childNodes[box.childNodes.length - 1]。
alert(box.firstChild.nodeValue); //獲取第一個子節點的文本內容
alert(box.lastChild.nodeValue); //獲取最後一個子節點的文本內容
3).ownerDocument 屬性
ownerDocument 屬性返回該節點的文檔對象根節點,返回的對象相當於 document。alert(box.ownerDocument === document); //true,根節點
4).parentNode、previousSibling、nextSibling 屬性
parentNode 屬性返回該節點的父節點;previousSibling 屬性返回該節點的前一個同級節點;nextSibling 屬性返回該節點的後一個同級節點。
alert(box.parentNode.nodeName); //獲取父節點的標簽名
alert(box.lastChild.previousSibling); //獲取前一個同級節點
alert(box.firstChild.nextSibling); //獲取後一個同級節點
5).attributes 屬性
attributes 屬性返回該節點的屬性節點集合。
document.getElementById(‘box‘).attributes //NamedNodeMap
document.getElementById(‘box‘).attributes.length; //返回屬性節點個數
document.getElementById(‘box‘).attributes[0]; //Attr,返回最後一個屬性節點
document.getElementById(‘box‘).attributes[0].nodeType; //2,節點類型
document.getElementById(‘box‘).attributes[0].nodeValue; //屬性值
document.getElementById(‘box‘).attributes[‘id‘]; //Attr,返回屬性為 id 的節點
document.getElementById(‘box‘).attributes.getNamedItem(‘id‘); //Attr
6).忽略空白文本節點(重點)
var body = document.getElementsByTagName(‘body‘)[0]; //獲取 body 元素節點
alert(body.childNodes.length); //得到子節點個數,IE3&&&非 IE
PS:在非 IE 中,標準的 DOM 具有識別空白文本節點的功能;而 IE 自動忽略了,如果要保持一致的子元素節點,需要手工忽略掉它。
法1:忽略空白文本節點
1 function filterWhiteNode(node) { 2 3 var ret = []; 4 5 for (var i = 0; i < node.length; i ++) { 6 7 if (node[i].nodeType === 3 && /^\s+$/.test(node[i].nodeValue)) { 8 9 continue; 10 11 } else { 12 13 ret.push(node[i]); 14 15 } 16 17 } 18 19 return ret; 20 21 }
//法2:移除空白文本節點(這個方法比較酷)
1 function removeWhiteNode(node) { 2 3 for (var i = 0; i < node.length; i ++) { 4 5 if (node[i].nodeType === 3 && /^\s+$/.test(node[i].nodeValue)) { 6 7 //下面第四部分會介紹移除節點方法removeChild() 8 9 node[i].parentNode.removeChild(node[i]); 10 11 } 12 13 } 14 15 return node; 16 17 }
問題:那麽如果 firstChild、lastChild、previousSibling 和 nextSibling 在獲取節點的過程中遇到空白節點,我們該怎麽處理掉呢?
HTML代碼部分:
<div id="box" >
<p>測試Div1</p>
<p>測試Div2</p>
<p>測試Div3</p>
</div>
JS代碼部分:
1 window.onload = function () { 2 3 var box = document.getElementById(‘box‘); 4 5 alert(removeWhiteNode(box).firstChild.nodeName); 6 7 }; 8 9 //移除空白節點 10 11 function removeWhiteNode(node) { 12 13 for (var i = 0; i < node.childNodes.length; i ++) { 14 15 if (node.childNodes[i].nodeType === 3 && /^\s+$/.test(node.childNodes[i].nodeValue)) { 16 17 node.childNodes[i].parentNode.removeChild(node.childNodes[i]); 18 19 } 20 21 } 22 23 return node; 24 25 }
四.節點操作
DOM 不單單可以查找節點,也可以創建節點、復制節點、插入節點、刪除節點和替換節點。
圖8
1).write()方法
write()方法可以把任意字符串插入到文檔中去。
document.write(‘<p>這是一個段落!</p>‘)‘; //輸出任意字符串
2).createElement()方法
createElement()方法可以創建一個元素節點。
document.createElement(‘p‘); //創建一個元素節點(僅是創建並未插入)
3).appendChild()方法
appendChild()方法講一個新節點添加到某個節點的子節點列表的末尾上。
var box = document.getElementById(‘box‘); //獲取某一個元素節點
var p = document.createElement(‘p‘); //創建一個新元素節點<p>
box.appendChild(p); //把新元素節點<p>添加子節點末尾
4).createTextNode()方法
createTextNode()方法創建一個文本節點。
var text = document.createTextNode(‘段落‘); //創建一個文本節點
p.appendChild(text); //將文本節點添加到子節點末尾
5).insertBefore()方法
insertBefore()方法可以把節點創建到指定節點的前面。
box.parentNode.insertBefore(p, box); //把<div>之前創建一個節點(註意要先獲取到父節點)
PS:insertBefore()方法可以給當前元素的前面創建一個節點,但卻沒有提供給當前元素的後面創建一個節點。那麽,我們可以用已有的知識創建一個 insertAfter()函數。
代碼實現部分:
1 function insertAfter(newElement, targetElement) { 2 3 //得到父節點,就是body,但是不能直接使用body,如果層次較多,父節點不一定是body 4 5 var parent = targetElement.parentNode; 6 7 //如果最後一個子節點是當前元素,那麽直接添加即可 8 9 if (parent.lastChild === targetElement) { 10 11 alert(‘‘); 12 13 parent.appendChild(newElement, targetElement) 14 15 } else { 16 17 //否則,在當前節點的下一個節點之前添加 18 19 //目標節點的前面添加,就可以用insertBefore;使用nextSibling獲取目標節點 20 21 parent.insertBefore(newElement,targetElement.nextSibling); 22 23 } 24 25 }
PS:createElement 在創建一般元素節點的時候,瀏覽器的兼容性都還比較好。但在幾個特殊標簽上,比如 iframe、input 中的 radio 和 checkbox、button 元素中,可能會在 IE6,7
以下的瀏覽器存在一些不兼容。解決方法如下(註意:以下代碼用到了瀏覽器檢測文件browserdetect.js但不需要深究此文件):
1 var body = document.getElementsByTagName(‘body‘)[0]; 2 3 if (BrowserDetect.browser == ‘Internet Explorer‘ && BrowserDetect.version <= 7) { 4 5 var input = document.createElement("<input type=\"radio\" name=\"sex\">"); 6 7 } else { 8 9 //標準瀏覽器,使用標準方式 10 11 var input = document.createElement(‘input‘); 12 13 input.setAttribute(‘type‘, ‘radio‘); 14 15 input.setAttribute(‘name‘, ‘sex‘); 16 17 } 18 19 20 21 body.appendChild(input); 22 23
6).repalceChild()方法
replaceChild()方法可以把節點替換成指定的節點。
box.parentNode.replaceChild(p,box); //把<div>換成了<p>
7).cloneNode()方法
cloneNode()方法可以把子節點復制出來。
var box = document.getElementById(‘box‘);
var clone = box.firstChild.cloneNode(true); //獲取第一個子節點,true 表示復制內容
box.appendChild(clone); //添加到子節點列表末尾
8).removeChild()方法
removeChild()方法刪除指定節點
box.parentNode.removeChild(box); //刪除指定節點
DOM系列基礎知識