七(2) . 最常見的JavaScript DOM方法實戰-文檔遍歷

我在七(1).DOM的核心對象:Node 分析最常見的JavaScript DOM方法一覽表 2篇日誌中,我詳細的對DOM核心對象Node的方法和屬性進行了分析和歸類,建立起了JavaScript DOM 基礎的知識體系圖,以便我們在今後的JavaScript DOM 學習中,更方便,更高效。

在我們學習之前,我還是把總結好的DOM知識體系表放在前面以方便我們查閱。

Node接口定義的節點類型都包含的特性和方法

特性和方法後面的 “冒號:” 緊跟的單詞是“返回值類型

Node 屬性
遍歷節點(短途旅行):
parentNode : Node
firstChild : Node
lastChild : Node
nextSibling : Node
previousSibling : Node
childNodes : NodeList
節點信息:
nodeName :String
nodeType :number
nodeValue :String
返回一個節點的根元素(文檔對象):
ownerDocument : Document
包含了代表一個元素的特性的Attr對象;僅用於Element節點:
attributes : NamedNodeMap
獲取對象層次中的父對象:
parentElement [IE] :Node
方法
修改文檔樹:
appendChild(Node newChild) : Node
insertBefore(Node newChild, Node refChild) : Node
removeChild(Node oldChild): Node
replaceChild(Node newChild, Node refChild) : Node
克隆一個節點:
cloneNode(boolean deep) : Node
刪除一個子節點:
removeNode(boolean removeChildren) : Node
判斷childNodes包含是否包含節點:
hasChildNodes() : boolean

Node

Document
屬性
自己的:
documentElement : Element
繼承 Node :
attributes, childNodes, firstChild, lastChild, nextSibling, nodeName, nodeType, nodeValue, ownerDocument, parentElement, parentNode, previousSibling
方法
自己的:
創建元素:
createElement(String tagName) : Element
createTextNode(String data) : Text
查找元素:
getElementById(String elementId) : Element
getElementsByTagName(String tagname) : NodeList
繼承 Node :
appendChild, cloneNode, hasChildNodes, insertBefore, removeChild, removeNode, replaceChild

Element
屬性
自己的:
tagName: String
繼承 Node :
attributes, childNodes, firstChild, lastChild, nextSibling, nodeName, nodeType, nodeValue, ownerDocument, parentElement, parentNode, previousSibling
方法
自己的:
屬性的讀寫:
getAttribute(String name) : String
setAttribute(String name, String value) : void
其它:
getElementsByTagName(String name) Stub : NodeList
normalize() Stub : void
removeAttribute(String name) : void
繼承 Node :
appendChild, cloneNode, hasChildNodes, insertBefore, removeChild, removeNode, replaceChild

訪問相關的節點

我們還是從 HTML頁面 最基本的組成元素討論起,

1
2
3
4
5
6
7
8
<!--<html>

<head>
<title>DOM Examlie</title>
</head>
<body>
<p>Hello World !</p>
</body>
</html>-->

我要訪問 <html>元素,你應該明白它是該文件的document元素,那你就可以使用document的documentElement屬性

1
2
3
var
 oHtml=
document.documentElement
;
//可以直接訪問<html>元素

alert ( "節點名稱 : " + oHtml.nodeName ) ; //節點名稱
alert ( "節點類型 : " + oHtml.nodeType ) ; //節點類型爲 1

獲取<head> 和 <body>元素

1
2
var
 oHead=
oHtml.firstChild
;
//HEAD節點

var oBody= oHtml.lastChild ; //BODY節點

也可以通過childNodes屬性,獲取<head> 和 <body>元素

1
2
3
4
var
 oHead=
oHtml.childNodes
.item
(
0
)
;
//HEAD節點

//var oHead=oHtml.childNodes[0];//簡寫,也有同樣的結果是HEAD節點
var oBody= oHtml.childNodes .item ( 1 ) ; //BODY節點
//var oBody=oHtml.childNodes.item(1);//簡寫,也有同樣的結果是BODY節點

注意:方括號標記其實是NodeList在javascript中的簡便實現。實際上正式的從childNodes列表中獲取子節點的方法是使用item()方法:

HTML DOM 中的專有屬性 document.body ,它常用於直接訪問元素

1
    var
 oBody=
document.body
;

既然我們都知道了以上節點對象的獲取方式,那我們用oHtml,oHead,oBody 這三個變量來確定一下它們之間的關係

1
2
3
4
5
alert
(
oHead.parentNode
==
oHtml)
;
//HEAD節點的父節點是BODY節點,返回 true 

alert ( oBody.parentNode == oHtml) ; //BODY節點的父節點是BODY節點,返回 true
alert ( oBody.previousSibling == oHead) ; //BODY節點的上一個兄弟節點是HEAD節點 ,返回 true
alert ( oHead.nextSibling == oBody) ; //HEAD節點的下一個兄弟節點是BODY節點,返回 true
alert ( oHead.ownerDocument == document) ; //返回一個節點的根元素(Document),HEAD節點是否指向該文檔,返回 true

通過上面的學習我們已經瞭解遍歷節點的最基本的方式, 也學會了如何找到某一個節點的兄弟節點及它的子節點。它們之間的關係你是否理解透徹了那?不是很明白也沒關係,下面就是我給出的節點關係圖

 


節點關係圖

節點之間的關係圖

複雜的節點遍歷

在上面的學習中我們好像沒有遇到過大的阻礙,下面我在一我的導航條爲例還討論節

點遍歷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div
 id
=
"menu"
>

<h1 > 我的導航條</ h1 >
<ul id = "nav" >
<li ><a href = "#" > HOME</ a ></ li >
<li ><a href = "#" > (X)Html / Css</ a ></ li >
<li ><a href = "#" > Ajax / RIA</ a ></ li >
<li ><a href = "#" > GoF</ a ></ li >
<li ><a href = "#" > JavaScript</ a ></ li >
<li ><a href = "#" > JavaWeb</ a ></ li >
<li ><a href = "#" > jQuery</ a ></ li >
<li ><a href = "#" > MooTools</ a ></ li >
<li ><a href = "#" > Python</ a ></ li >
<li ><a href = "#" > Resources</ a ></ li >
</ ul >
</ div >

DOM-Properties

我又給出了一幅節點之間的關係圖,是針對我的導航條。

首先我想把看一下我的導航條下有多少個子節點。
我第一想到的是前面我學過的查找元素的2種方法:

getElementById() # 通過ID屬性查找元素
該方法將返回一個與那個有着給定id屬性值的元素節點相對應的對象。
getElementsByTagName() # 通過標籤名稱查找元素
該方法返回一個對象數組,每個對象分別對應着文檔裏有着給定標籤的一個元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<
script type=
"text/javascript"
>

/*
通過ID屬性查找元素 ,用的是文檔對象的getElementById()方法,
查找到我們想要的元素對象,根據這個節點元素的 childNodes 屬性,
遍歷出所有的子節點對象。
*/

function queryElementsId( ) {
var elemensArray, nav, nav_list;
elemensArray= [ ] ;
nav= document.getElementById ( "nav" ) ;
/*注意IE和FF中處理Text節點上存在着一些差異*/
nav_list= nav.childNodes ;
for ( var i= 0 ; i< nav_list.length ; i++ ) {
elemensArray[ elemensArray.length ] = nav_list[ i] ;
//elemensArray.push(nav_list[i]); //同上一樣的結果
}
return elemensArray;
 
}
/*
我們觀察到我的導航條是有規律的,是用無序列表元素組成的,只有定位到 &lt;ul&gt;元素
;然後把getElementsByTagName()方法可以返回相同元素對象的集合,
查用它找一組元素,太方便了。
*/

function queryElementsTagName( ) {
var elemensArray, nav, nav_list;
elemensArray= [ ] ;
var nav= document.getElementById ( "nav" ) ;
var nav_list= nav.getElementsByTagName ( "li" ) ; //返回相同的一組元素
for ( var i= 0 ; i< nav_list.length ; i++ ) {
elemensArray[ elemensArray.length ] = nav_list[ i] ;
//elemensArray.push(nav_list[i]); //同上一樣的結果
}
return elemensArray;
 
}
</ script>

節點遍歷

那我們接下來,測一下是否是我們想要的東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
<
script type=
"text/javascript"
>

window.onload = function ( ) {
/*第一個方法*/
var list= queryElementsId( ) ;
/*第二個方法*/
//var list= queryElementsTagName();
var s= "" ;
for ( var i= 0 ; i< list.length ; i++ ) {
s+= list[ i] .nodeName + "/n " ;
}
alert ( s) ;
}
</ script>

注意:爲什麼把處理的方法放到window.onload中的原因我就不多說了。

我有一個專題中專門介紹了各種window.onload的解決方案。

請看 關於window.onload加載的多種解決方案
日誌篇。

先看一下第一個方法 queryElementsId() 好像我們在IE中沒有發現有什麼問題,那我們在Firefox中看一下是否也是我們想要的結果。

IE中的DOM遍歷:

IE DOM

FF中的DOM遍歷:

FF DOM

通過上面的測試,我們發現了一個嚴重的問題。

那就是,不同的瀏覽器在判斷何爲Text節點上存在着一些差異,例如在A級瀏覽器中的FF和IE就有很大的差異,

FF會把元素之間的空白、換行、tab都是Text節點,

IE下會把空白全部忽略掉,只有內聯元素(如em,span)後的換行、空格、tab會被認爲是一個Text。

既然遇到了問題那我們就得解決問題,問題的根源我們也知道了,那相應的解決方案就好做了。

我這裏整理出3種方法,供大家參考。

方法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<
script type=
"text/javascript"
>

/*
《精通javascript》上提供了一個函數,用於處理xm中的這些空格,其作用原理就是找出文本節點,並刪除這些節點,以達到刪除這些空格的目的。
*/

 
function cleanWhitespace( element) {
//如果不提供參數,則處理整個HTML文檔
element = element || document;
//使用第一個子節點作爲開始指針
var cur = element.firstChild ;
 
//一直到沒有子節點爲止
while ( cur != null ) {
//如果節點爲文本節點,應且包含空格
if ( cur.nodeType == && ! //S/ .test ( cur.nodeValue ) ) {
//刪除這個文本節點
element.removeChild ( cur ) ;
 
//否則,它就是一個元素
} else if ( cur.nodeType == 1 ) {
//遞歸整個文檔
cleanWhitespace( cur ) ;
}
 
cur = cur.nextSibling ; //遍歷子節點
}
}
</ script>

方法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<
script type=
"text/javascript"
>

 
/*
最後,利用數組寫了一個函數,能夠有效的處理dom中的空格,其原理就是將一個元素的的父元素找出來,然後通過它父元素的childNodes屬性找出該元素的所有兄弟元素。遍歷該元素和它的兄弟元素,將所有元素節點放在一個數組裏。這樣調用這個數組,就只有元素節點而沒有文本節點,也就沒有了討厭的空格.
 
*/

 
function cleanWhitespaces( elem) {
//如果不提供參數,則處理整個HTML文檔
var elem = elem || document;
var parentElem = elem.parentNode ; //返回一個節點的父類節點
var childElem = parentElem.childNodes ; //返回一個節點的子節點的節點列表
var childElemArray = new Array;
for ( var i= 0 ; i< childElem.length ; i++ ) {
if ( childElem[ i] .nodeType == 1 ) { //把所有節點是元素節點類型的節點存放到數組裏
childElemArray.push ( childElem[ i] ) ;
}
}
return childElemArray;
}
</ script>

方法三:推薦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<
script type=
"text/javascript"
>

/*
原理是對元素的所有的子節點做一個遍歷。然後做一個判斷,如果是子元素節點(nodeType = 1),則遍歷該子元素的所有的子節點,用遞歸檢查是否包含空白節點;如果處理的子節點是文本節點(nodeType = 3),則檢查是否是純粹的空白節點,如果是,就將它從xml對象中刪除。
*/

function removeWhitespace( xml) {
var loopIndex;
 
for ( loopIndex = 0 ; loopIndex < xml.childNodes .length ; loopIndex++ ) {
var currentNode = xml.childNodes [ loopIndex] ;
if ( currentNode.nodeType == 1 ) {
removeWhitespace( currentNode) ;
}
 
if ( ( ( /^/s+$/ .test ( currentNode.nodeValue ) ) ) && ( currentNode.nodeType == 3 ) ) {
xml.removeChild ( xml.childNodes [ loopIndex-- ] ) ;
}
}
}
</ script>

好了,我們在驗證一下,#Text節點問題是否處理掉了。那我們就用方法3 中removeWhitespace(nav)方法來處理queryElementsId()方法中的#Text節點問題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<
script type=
"text/javascript"
>

function queryElementsId( ) {
var elemensArray, nav, nav_list;
elemensArray= [ ] ;
nav= document.getElementById ( "nav" ) ;
/*處理#Text節點問題*/
removeWhitespace( nav) ;
 
/*注意IE和FF中處理Text節點上存在着一些差異*/
nav_list= nav.childNodes ;
for ( var i= 0 ; i< nav_list.length ; i++ ) {
elemensArray[ elemensArray.length ] = nav_list[ i] ;
//elemensArray.push(nav_list[i]); //同上一樣的結果
}
return elemensArray;
 
}
</ script>

正如我想看到的結果,IE和FF中都OK 了

FF IE DOM

一個比較通用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<
script type=
"text/javascript"
>

function text( elem) {
var t= "" ;
//如果傳入的是元素,則繼續遍歷其子元素
//否則假定它是一個數組
elem= elem.childNodes || elem;
//遍歷所有子節點
for ( var i= 0 ; i< elem.length ; i++ ) {
//如果不是元素,追加其文本值
//否則,遞歸遍歷所有元素的子節點
t+= elem[ i] .nodeType != 1 ? elem[ i] .nodeValue : text( elem[ i] .childNodes ) ;
 
}
//返回比配的文本
return t;
}
</ script>

《我的導航條》中相關節點的訪問(父節點,子節點,兄弟節點)

讓我再熟悉一下上面學過的知識點,實踐是檢驗真理的唯一標準。
那就開始吧!

DOM-Properties

用元素節點的DOM屬性遍歷DOM樹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<
script type=
"text/javascript"
>

window.onload = function ( ) {
/*定位想要的節點*/
var nav= document.getElementById ( "nav" ) ;
/*查找父節點*/
var p_n= nav.parentNode ;
alert ( "父節點的元素名稱:" + p_n.nodeName ) ;
 
/*處理FF遍歷節點中的#Text */
removeWhitespace( nav) ; //移除所有的空Text節點
 
/*查找子節點*/
var c_n_f= nav.firstChild ; //第一個節點對象
//var c_n_f=nav.childNodes[0];//同上一樣的結果
var c_n_l= nav.lastChild ; //最後一個節點對象
//var c_n_l=nav.childNodes[nav.childNodes.length-1];//同上一樣的結果
alert ( "第一個節點:" + c_n_f.nodeName + " " + "最後一個節點 :" + c_n_l.nodeName ) ;
/*查找兄弟節點 或叫 相鄰節點 */
/*用nextSibling和PreviousSibling必須有一個參考點,這樣指針才知道自己往那裏移動*/
var c_n_s= c_n_f.nextSibling ; //第一個節點的下一個節點
alert ( "第一個節點的下一個節點:" + c_n_s.innerHTML + "/n " + "節點中包含的HTML內容: " + c_n_s.nodeName ) ;
 
}
 
</ script>

遍歷節點的相關屬性和輔助的方法,我們大部分都應用到了,有更多的方法和技巧還等待我們去學習和發現。

寫到這裏,既然標準的previousSibling,nextSibling,firstChild,lastChild,parentNode 遍歷方法有瀏覽器不兼容問題。我上面的解決方案是去掉遍歷元素的相關空的#Text節點,是一個好的解決方案,但是使用起來不方便,我們何不自己寫一些遍 歷節點的方法來代替標準的的previousSibling,nextSibling,firstChild,lastChild, parentNode。

我們的思路是利用元素是nodeType屬性來判斷元素是節點類型中那種節點類型,在DOM節點中我最常用的是元素節點,文本節點,屬性節點,對應的類型值是
元素節點 nodeType=1 or ELEMENT_NODE, 文本節點 nodeType=2 or ATTRIBUTE_NODE,屬性節點 nodeType=3 or TEXT_NODE,但是IE中並不支持命名常量,那就用數值吧,再配合標準的遍歷屬性。完全可以自己生產一些輔助函數來取代標準的遍歷方式。

以下一系列的輔助函數可以幫助您,他們能取代標準的previousSibling,nextSibling,firstChild,lastChild,parentNode;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<
script type=
"text/javascript"
>

//---------DOM 遍歷,如果元素沒找到則返回null---------//
//---查找相關元素的前一個兄弟元素---//
function prev( elem) {
do {
elem= elem.previousSibling ;
} while ( elem && elem.nodeType != 1 ) ;
return elem;
}
//---查找相關元素的下一個兄弟元素---//
function next( elem) {
do {
elem= elem.nextSibling ;
} while ( elem && elem.nodeType != 1 ) ;
return elem;
}
//---查找第一個子元素的函數---//
function first( elem) {
elem= elem.firstChild ;
return elem && elem.nodeType != 1 ? next( elem) : elem;
}
//---查找最後一個子元素的函數---//
function last( elem) {
elem= elem.lastChild ;
return elem && elem.nodeType != 1 ? prev( elem) : elem;
}
//---查找父級元素的函數---//
//num是父級元素的級次,parent(elem,2)等價於
function parent( elem, num) {
num= num|| 1 ;
for ( var i= 0 ; i< num; i++ ) {
if ( elem!= null ) {
elem= elem.parentNode ;
}
}
return elem;
}
</ script>

在下一節中我們來討論如何修改文檔樹 .

發佈了30 篇原創文章 · 獲贊 0 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章