我在七(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
>
|
我又給出了一幅節點之間的關係圖,是針對我的導航條。
首先我想把看一下我的導航條下有多少個子節點。
我第一想到的是前面我學過的查找元素的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;
}
/*
我們觀察到我的導航條是有規律的,是用無序列表元素組成的,只有定位到 <ul>元素
;然後把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遍歷:
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 了
一個比較通用的方法
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屬性遍歷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>
|
在下一節中我們來討論如何修改文檔樹
.