我在七(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>
|
在下一节中我们来讨论如何修改文档树 .