DOM
节点层次
Element类型
Element类型用于表现XML或HTML元素,提供了对元素签名、子节点及特性的访问。Element节点的特征如下:
- nodeType的值为1
- nodeName的值为元素的标签名
- nodeValue的值为null
- parentNode可能是Document或Element
- 子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference
要访问元素的标签名,可以使用nodeName或者tagName属性。这两者返回的结果是相同的。在HTML中,标签名始终都是大写表示。而在XML(有时也包括XHTML)中,标签名则始终会与源代码中的保持一致。所以一般都会先转成同样的格式再去比较:
if (element.tagName.toLowerCase() == "div") {
//code
}
HTML元素
- 所有HTML元素都由HTMLElement类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement类型直接继承自ELement并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性:
- id,元素在文档中的唯一标识符。
- title,有关元素的附加说明信息,一般通过工具提示条显示出来。
- lang,元素内容的语言代码,一般很少使用。
- dir,语言的方向,值为”ltr”(left-to-right)或者”rtl”,一般很少使用。
- className,与元素的class特性对应,即为元素指定的CSS类。没有将这个属性命名为class,是因为class是保留字。
<!DOCTYPE html>
<html>
<head>
<title>HTML Elements Example</title>
</head>
<body>
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr">Some text</div>
<input type="button" value="Get Values" onclick="getValues()">
<input type="button" value="Set Values" onclick="setValues()">
<script type="text/javascript">
var div = null;
function getValues(){
if (div == null) {
div = document.getElementById("myDiv");
}
alert(div.id); //"myDiv"
alert(div.className); //"bd"
alert(div.title); //"Body text"
alert(div.lang); //"en"
alert(div.dir); //"ltr"
}
function setValues(){
if (div == null) {
div = document.getElementById("myDiv");
}
div.id = "someOtherId";
div.className = "ft";
div.title = "Some other text";
div.lang = "fr";
div.dir ="rtl";
}
</script>
</body>
</html>
- 并不是对所有属性的修改都会在页面中直观的表现出来。对id和lang的修改对用于而言是不可见的。而title只会在鼠标移动到节点上才会显示。对dir的修改会在属性被重写的那一刻,立即影响页面中文本的左、右对齐方式。修改className,如果新类关联了新的CSS样式,也会马上体现出来。
- 所有HTML元素都是由HTMLElement或者其更具体的子类来表示的。在书上了263页,有具体的元素对应的类型表。
取得特性
- 每个元素都有一或多个特性,操作特性的DOM方法有三个,分别是getAttribute()、setAttribute()、removeAttribute()。这三者看看名字就知道怎么用了,下面先讲一下getAttribute()。
- 传给getAttribute()的特性名必须和实际的特性名相同(不区分大小写)。不存在就返回null。
- 任何元素的所有特性,也都可以通过DOM元素本身的属性来访问。不过只有公认的(非自定义的)特性才会以属性的形式添加到DOM对象中。
<!DOCTYPE html>
<html>
<head>
<title>Element Attributes Example 2</title>
</head>
<body>
<div id="myDiv" my_special_attribute="hello!" align="left">Some text</div>
<input type="button" value="Get Values" onclick="getValues()">
<script type="text/javascript">
var div = null;
function getValues(){
if (div == null) {
div = document.getElementById("myDiv");
}
alert(div.id); //"myDiv"
alert(div.my_special_attribute); //undefined
alert(div.align); //"left"
//align是div的公认属性,而my_special_attribute不是
}
</script>
</body>
</html>
- 有两种特殊的特性虽然有对应的属性名,但属性的值与通过getAttribute()返回的值并不相同。一个是style,一个是事件处理程序。style通过getAttribute()只会返回一个字符串,而通过属性名访问则会返回一个对象。事件处理程序的getAttribute()只会返回一个字符串,而通过属性名访问则会返回一个function。
<!DOCTYPE html>
<html>
<head>
<title>Element Attributes Example 2</title>
</head>
<body>
<div id="myDiv" onclick="alert('hello');" style="color: #0F0;">Some text</div>
<input type="button" value="Get Values" onclick="getValues()">
<script type="text/javascript">
var div = document.getElementById("myDiv");
function getValues(){
alert(div.onclick);//function onclick(event){alert('hello');}
alert(typeof div.onclick);//function
alert(div.getAttribute("onclick"));//alert('hello');
alert(typeof div.getAttribute("onclick"));//string
alert(div.style);//[object CSSStyleDeclaration]
alert(typeof div.style);//object
alert(div.getAttribute("style"));//color: #0F0;
alert(typeof div.getAttribute("style"));//string
}
</script>
</body>
</html>
设置特性
- 设置元素的特性一般有两种写法,对于公认的(非自定义的)特性,可以直接通过属性名去修改。也可以通过setAttribute()传入两个参数去修改(这种方式设置的特性名会被自动转换成小写形式)。后者可以自定义特性。如果用前者自定义属性,该属性不会自动成为元素的特性。另外通过setAttibuteNode()方法传入一个Attr对象(后面介绍)也可以设置新的特性。
div.id = "someId";
div.align = "left";
div.mycolor = "red";//这种写法不会成为元素的特性。
alert(div.getAttribute("mycolor"));//null(IE8及低版本会正常显示)
- 总结一下,但凡是自定义的属性或者特性都不会自动设置对方。
- removeAttribute()就厉害了,不仅会清除特性的值,而且也会从元素中完全删除特性。
attributes属性
- Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与NodeList类型,也是一个”动态”集合。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。NamedNodeMap对象拥有下列方法。
- getNamedItem(name): 返回nodeName属性等于name(不区分大小写)的节点
- removeNamedItem(name): 从列表中移除nodeName属性等于name的节点
- setNamedItem(node): 向列表中添加节点,以节点的nodeName属性为索引。
- item(pos): 位于数字pos位置的节点。
- attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。除了使用getNamedItem(name)、 item(pos)获取特性节点,还可以直接使用方括号语法获取。
- 由于上面的方法写起来不太方便,所以一般开发就会使用上面介绍的方法,但是要遍历元素节点的特性,就得用到attributes了。
for (i=0, len=element.attributes.length; i < len; i++){
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
pairs.push(attrName + "=\"" + attrValue + "\"");
}
- 不同浏览器返回的特性顺序可能不一致,而IE7及更早的版本会返回HTML元素中所有可能的特性。针对IE7的问题,可以对上面的函数加以改进。每个特性节点都有一个名为specified的属性,这个属性的值如果是true,则意味着要么是在HTML中指定了该特性,要么是通过setAttribute设置了该特性。而未设置过的特性的该值都为false。在其他浏览器中,根本不会生成该特性节点,所以所有存在的特性节点的specified都为true。
for (i=0, len=element.attributes.length; i < len; i++){
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
if (element.attributes[i].specified){
pairs.push(attrName + "=\"" + attrValue + "\"");
}
}
创建元素
- 使用document.createElement()方法可以创建新元素。这个方法只接收一个参数,即要创建的元素的标签名。这个标签名在HTML中不区分大小写,而在XML(包括XHTML)中则是区分大小写的。在使用该方法创新新元素的同时,也为新元素设置了ownDocument属性。
- 在IE中可以使用另外一种方式使用createElement(),即为这个方法传入完整的元素标签,如下:
var div = document.createElement("<div id=\"mynewdivid\"></div>");
- 这种方式的缺陷很多,不推荐使用。
元素的子节点
- 看下面的代码:
<ul id="myList">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
- 如果是IE来解析这些代码,那么这个元素会有3个子节点,分别是3个li元素。如果在其他浏览器中,ul元素会有7个元素,另外包含了4个文本节点。所以在用childNodes去遍历所有子节点时,千万不要忘记有这个小小的差别。我们一般用检测子节点的nodeType(为1)来判断是不是我们需要的元素节点。
- 另外我们还可以用下面的代码获得所有的li元素:
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");
//不过该方法会返回ul元素下所有层次的所有li元素。
Text类型
- 文本节点由Text类型表示,包含的是可以按照字面解释的纯文本内容。纯文本可以包含转义后的HTML字符,但不能包含HTML代码。Text节点具有以下特征:
- nodeType的类型为3
- nodeName的值为#text
- nodeValue的值为文本内容
- parentNode是一个Element
- 没有子节点
- 可以通过nodeValue或data属性访问文本内容,他们包含的值相同。使用下列的方法可以操作节点中的文本。
- appendData(text),添加text到末尾
- deleteData(offset, count),从offset位置开始删除count个字符
- insertData(offset, text),从offset位置插入text
- replaceData(offset, count, text),删除加插入
- splitText(offset),从offset处将文本节点分成2个文本节点
- substringData(offset, count),截取字符串
- 除了这些方法,还有一个length属性,保存着文本节点中字符的数目。
- 在修改文本节点时,字符串会经过HTML(XML)编码。大于号等符号会被转义。但是目前比较新的浏览器(例如我的chrome)则会显示原来的内容:
function changeText(){
var div = document.getElementById("myDiv");
div.firstChild.nodeValue = "Some <strong>other</strong> message";
}
创建文本节点
- 可以使用document.createTextNode()创建新文本节点,该方法接收一个参数,即要插入的文本节点内容。下面是例子:
function addNode(){
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
}
规范化文本节点
- 如上所示,可以通过splitText()或者createTextNode(),然后appendChild()方式造成相邻的同胞文本节点。这种行为很容易造成混乱,会分不清哪个文本节点表示哪个字符串。因此就催生了一个Node类型的方法(前面提到过)normalize()。如果文本节点不包含文本,或者接连出现两个文本节点。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除。如果找到接连出现两个文本节点,就合并。且nodeValue的值也会合并。
function addNode(){
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
alert(element.childNodes.length); //2
element.normalize();
alert(element.childNodes.length); //1
alert(element.firstChild.nodeValue); //"Hello World!Yippee!"
}
Comment类型
- 注释在DOM中是通过Comment类型来表示的。Comment节点具有以下特征:
- nodeType的值为8
- nodeName的值为#comment
- nodeValue的值是注释的内容
- parentNode可能是Document或Element
- 没有子节点
- Comment类型与Text类型继承自相同的基类,因此他拥有除splitText()之外的所有字符串操作方法。与Text类型相似,也可以通过nodeValue或data属性来取得注释的内容。另外还有createComment()方法创建注释节点,但是由于这种类型鲜有人用,所以这里不再讨论。开发的时候不要忽略注释也是一个节点就可以了。
CDATASection类型
- 该类型只针对XML文档,表示的是CDATA区域。与Comment类似,CDATASection类型继承自Text类型,因此拥有除splitText()之外的所有字符串操作方法。特征如下:
- nodeType的值为4
- nodeName的值为#cdata-section
- nodeValue的值是CDATA区域中的内容
- parentNode可能是Document或Element
- 没有子节点
- CDATA区域只会出现在XML文档中,因此多数浏览器都会把CDATA区域错误的解析为Comment或Element(Chrome中会当成Comment)。
- 相应的,在XML文档中,可以使用document.createCDataSection()来创建CDATA区域。
DocumentType类型
该类型在Web浏览器中并不常用,仅有Firefox、Safari和Opera支持他(也许目前的最新浏览器也可以使用,因为我的Chrome也可以用)。DocumentType包含着与文档的doctype有关的所有信息,它具有以下特征:
- nodeType的值为10
- nodeName的值为doctype的名称
- nodeValue的值为null
- parentNode为Document
- 没有子节点
在DOM1级中,DocumentType对象不能动态创建,而只能通过解析文档代码的方式来创建。支持他的浏览器会吧DocumentType对象保存在document.doctype中。DOM1级描述了3个属性:name、entities和notations。其中name表示文档类型的名称。entities 是由文档类型描述的实体的NamedNodeMap对象。notations是由文档类型描述的符号的NamedNodeMap对象。后两个属性一般没有什么用。第一个属性则是用来保存
DocumentFragment类型
- 该类型只是一个节点”仓库”,在文档中没有对应的标记。DOM规定文档片段是一种轻量级的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。特征如下:
- nodeType的值为11
- nodeName的值为#document-fragment
- nodeValue的值为null
- parentNode的值为null
- 子节点可以是Element、ProcessingInstruction、Comment、Text、CDATASection或EntityReference
- 可以使用document.createDocumentFragment()来创建文档片段。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点,也不会从浏览器中再看到该节点。添加到文档片段中的新结点同样也不属于文档树。可以通过appendChild()或insertBefore()将文档片段中内容添加到文档中。在将文档片段作为参数传递给这2个方法时,实际上只会将文档片段的子节点添加到相应位置上(这也说明了该类型为什么没有parentNode!)。
<!DOCTYPE html>
<html>
<head>
<title>Document Fragment Example</title>
</head>
<body>
<ul id="myList"></ul>
<input type="button" value="Add Items" onclick="addItems()">
<input type="button" value="remove Items" onclick="removeItems()">
<input type="button" value="restore Items" onclick="restoreItems()">
<script type="text/javascript">
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
function addItems(){
var li = null;
for (var i=0; i < 3; i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li);
}
ul.appendChild(fragment);
alert("fragment length:" + fragment.childNodes.length);
}
function removeItems(){
var length = ul.childNodes.length;
for (var i=0; i < length; i++){//注意不能写成i < ul.childNodes.length,因为ul是动态的
fragment.appendChild(ul.firstChild);
}
alert("fragment length:" + fragment.childNodes.length);
}
function restoreItems(){
ul.appendChild(fragment);
alert("fragment length:" + fragment.childNodes.length);
}
</script>
</body>
</html>
- 通过上面的代码,我们可以将DocumentFragment类型理解成文档的回收站,嘿嘿。
Attr类型
元素的特性在DOM中以Attr类型来表示。在所有浏览器中(包括IE8),都可以访问Attr类型的构造函数和原型。从技术角度讲,特性就是存在于元素的attributes属性中的节点。特性节点具有下列特征:
- nodeType的值为11
- nodeName的值是特性的名称
- nodeValue的值是特性的值
- parentNode的值为null(这里我觉得其实可以为Element)
- 在HTML中没有子节点,在XML中子节点可以是Text或EntityReference
尽管特性节点也是节点,但却不被认为是DOM文档树的一部分(这也是其parentNode为null的原因)。我们只要使用getAttribute()、setAttribute()、removeAttribute()去操作他们就行了。
- 前面已经提到过,Attr对象有3个属性,name、value和specified。其中name是特性名称(与nodeName相同),value是特性值(与nodeValue相同),而specified是一个布尔值,用以区别特性是在代码中指定的还是默认的。
- 使用document.createAttribute()并传入特性的名称可以创建新的特性节点。例如要为元素添加align特性,可以使用下面的代码:
function assignAttribute(){
var element = document.getElementById("myDiv");
var attr = document.createAttribute("align");
attr.value = "left";
//使用setAttributeNode()方法添加特性节点
element.setAttributeNode(attr);
alert(element.attributes["align"] === element.getAttributeNode("align"));//true
alert(element.attributes["align"].value); //"left"
//使用getAttributeNode()方法获取特性节点
alert(element.getAttributeNode("align").value); //"left"
alert(element.getAttribute("align")); //"left"
}