1 何为DOM?
www.w3.org官方的解释是:DOM 是HTML和XML文档的编程API。它定义了文档的逻辑结构和访问、操纵文档的方法。利用这组API,程序员可以对XML或HTML文档中的元素和内容进行任意的增删改查。这组API是与平台无关的,而且也是与编程语言无关的。这里的“对象模型”的含义取之于面向对象的编程思想。既然是对象就不仅包含属性,也包含操作属性的方法。所以DOM标准定义了这些属性和方法的名字及含义。
DOM包括DOM Core和DOM HTML两部分,DOM Core是用来操作XML的部分,也是操作HTML的基础。DOM HTML则是在DOM Core的基础上专门针对HTML增加了更多的对象、属性、方法。
2 DOM中节点(Node)的类型与继承
DOM把文档视为由Node构成的层级结构。构成文档的Node有很多类型,DOM Core 1中就定义了12中节点类型,不同节点类型具有不同的属性和方法。这12中节点类型如下:
- Document--文档节点,代表整个文档,可以有子节点,只能有一个元素类型的子节点。
- DocumentFragment
- DocumentType--不能有子节点
- EntityReference
- Element--元素节点,这也许是最重要的节点类型了吧
- Attr-属性节点
- ProcessingInstruction
- Comment
- Text--文本节点,不能有子节点
- CDATASection
- Entity
- Notation
DOM定义了上述每种节点类型的属性和方法,这里把Node这祖先类型和具体的Element类型、以及Attr类型的定义摘抄如下:
interface Node {
// NodeType
const unsigned short ELEMENT_NODE = 1;
const unsigned short ATTRIBUTE_NODE = 2;
const unsigned short TEXT_NODE = 3;
const unsigned short CDATA_SECTION_NODE = 4;
const unsigned short ENTITY_REFERENCE_NODE = 5;
const unsigned short ENTITY_NODE = 6;
const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
const unsigned short COMMENT_NODE = 8;
const unsigned short DOCUMENT_NODE = 9;
const unsigned short DOCUMENT_TYPE_NODE = 10;
const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
const unsigned short NOTATION_NODE = 12;
readonly attribute DOMString nodeName;
attribute DOMString nodeValue;
// raises(DOMException) on setting
// raises(DOMException) on retrieval
readonly attribute unsigned short nodeType;
readonly attribute Node parentNode;
readonly attribute NodeList childNodes;
readonly attribute Node firstChild;
readonly attribute Node lastChild;
readonly attribute Node previousSibling;
readonly attribute Node nextSibling;
readonly attribute NamedNodeMap attributes;
readonly attribute Document ownerDocument;
Node insertBefore(in Node newChild,
in Node refChild)
raises(DOMException);
Node replaceChild(in Node newChild,
in Node oldChild)
raises(DOMException);
Node removeChild(in Node oldChild)
raises(DOMException);
Node appendChild(in Node newChild)
raises(DOMException);
boolean hasChildNodes();
Node cloneNode(in boolean deep);
};
interface Element : Node {
readonly attribute DOMString tagName;
DOMString getAttribute(in DOMString name);
void setAttribute(in DOMString name,
in DOMString value)
raises(DOMException);
void removeAttribute(in DOMString name)
raises(DOMException);
Attr getAttributeNode(in DOMString name);
Attr setAttributeNode(in Attr newAttr)
raises(DOMException);
Attr removeAttributeNode(in Attr oldAttr)
raises(DOMException);
NodeList getElementsByTagName(in DOMString name);
void normalize();
};
interface Attr : Node {
readonly attribute DOMString name;
readonly attribute boolean specified;
attribute DOMString value;
};
对于DOM HTML部分,则针对HTML对一些类进行了进一步的扩展,如对Element进行扩展,产生了HTMLElement类,如下:
interface HTMLElement : Element {
attribute DOMString id;
attribute DOMString title;
attribute DOMString lang;
attribute DOMString dir;
attribute DOMString className;
};
HTML中的各种具体的元素节点也都从HTMLElement进行了进一步的扩展,如超链接标签<a >对应的HTMLAnchorElement:
interface HTMLAnchorElement : HTMLElement {
attribute DOMString accessKey;
attribute DOMString charset;
attribute DOMString coords;
attribute DOMString href;
attribute DOMString hreflang;
attribute DOMString name;
attribute DOMString rel;
attribute DOMString rev;
attribute DOMString shape;
attribute long tabIndex;
attribute DOMString target;
attribute DOMString type;
void blur();
void focus();
};
3 令人费解的文本节点---html文档中节点类型实例分析
为了弄清楚HTML文档中的节点关系,我们针对一段html代码进行分析。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="container">
我是文本节点
<p class="myclass">我也是文本类型的节点</p>
<br />
</div>
<script>
var div = document.getElementById('container');
var childs = div.childNodes;
for(i=0; i<childs.length; i++)
{
document.write(i + "--节点类型:" + childs[i].nodeType +
", 节点名称:" + childs[i].nodeName +
", 节点值:" + childs[i].nodeValue +
", 节点值长度:" + (childs[i].nodeValue==null?"0":childs[i].nodeValue.length)
+"<br/>");
}
</script>
</body>
</html>
这段js代码的输出结果如下:
1--节点类型:1, 节点名称:P, 节点值:null, 节点值长度:0
2--节点类型:3, 节点名称:#text, 节点值: , 节点值长度:3
3--节点类型:1, 节点名称:BR, 节点值:null, 节点值长度:0
4--节点类型:3, 节点名称:#text, 节点值: , 节点值长度:2
如果你能算出这段js代码的输出结果,那么说明对html文档结构与DOM的对应关系有比较细致的了解了,如果和你想的不一样,那么请继续看下面的分析:
首先是0号子节点:节点类型是文本节点,文本节点的节点值就是文本本身。对应“我是文本节点”这段文本,为什么长度是12而不是6呢?明明只有6个字啊。请注意这段文字前后的空格,其前面有2个Tab字符,下一行的前面也有两个Tab字符,这样就是6+2+2=10了,剩下的2个在哪里呢?那就是两个换行符。也就是说这个文本节点的内容其实是:
\n\t\t我是文本节点\n\t\t
,正好是12个字符。
再看1号节点,类型是元素节点,对应html中的<p></p>标签,元素标签的节点值都是null。
再看2号节点,是文本节点,基于0号节点的分析,我们知道这个文本的内容就是 \n\t\t,正好3个字符。
再看3号节点,是元素节点,对应<br/>标签,元素节点的节点值都是null。
再看4号节点,文本节点,对应 \n\t,正好2个字符。
由此可见,虽然html对于空白和换行直接忽略,但是使用DOM解析的时候,这些空白和换行会无故增加很多节点,增加浏览器端CPU和内存损耗。
4 DOM有时候会侵犯CSS地盘
css规定了html文档中元素如何呈现,注意这里是“元素”而不是节点。因为CSS选择器只能选择html中的元素节点,而不能选择其他类型的节点,如文本节点。那么为什么可以改变文本的显示呢?这是因为CSS是具有继承性的,文本节点必然是元素节点的子节点,所以它继承了元素节点的CSS特性。
由于选择器不能选择文本节点,所以处于同一元素节点下面的两个文本节点不能有各自独立的CSS特征值。如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
body{color:red;}
</style>
</head>
<body>
我是第一段文本
<br/>
我是第二段文本
</body>
</html>
这两个文本节点 ,都是body这个元素节点的子节点,我们无法通过css选择器分别为其指定显示特征。可见, 元素节点是具有CSS特征的最小单位。
除此之外,css的选择器有时候不能满足用户需求,如一个表格的奇数行和偶数行采用不同的背景色,虽然可以通过为每一行增加class属性来通过css完成,但是一旦表格行数巨大,这将导致非常烦躁。此时使用DOM来动态设置反而更加简单。也许未来CSS的选择器能力将更加灵活强大,那样就没有必要让DOM来干这些”不正当“业务了。
5 属性节点的两种读写方式间的差别
直接上一个测试代码,看看属性节点到底什么意思。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="container" style="color:red;"></div>
<script>
var div = document.getElementById('container');
var childs = div.attributes;
for(i=0; i<childs.length; i++)
{
document.write(i + "--节点类型:" + childs[i].nodeType +
", 节点名称:" + childs[i].nodeName +
", 节点值:" + childs[i].nodeValue +
", 节点值长度:" + (childs[i].nodeValue==null?"0":childs[i].nodeValue.length)
+"<br/>");
}
</script>
</body>
</html>
输出结果为:
0--节点类型:2, 节点名称:id, 节点值:container, 节点值长度:9
1--节点类型:2, 节点名称:style, 节点值:color:red;, 节点值长度:10
1--节点类型:2, 节点名称:style, 节点值:color:red;, 节点值长度:10
属性节点的类型代号为2,节点名称就是键名,节点的值为字符串。属性节点分为两种,一种是DOM Core和DOM HTML中规定的,如HTMLElement类规定了id,title等属性;另一种是程序员自定义的属性,键名可以是任意字符串。对于自定义的属性,只能通过getAttribute()来读取,通过setAttribute()来设置。而对于第一种属性来说,除了通过getAttribute()和setAttribute()来读写外,还可以直接使用点语法来读写,如下所示:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<input type="text" id="name" title="张三" />
<script>
var input = document.getElementById('name');
input.title='李四';
//input.setAttribute('title', '李四');
var name1 = input.getAttribute('title');
var name2 = input.title;
document.write(name1 + "," + name2);
</script>
</body>
</html>
上面两种方式,都可以改变input的title属性,效果完全相同。但是这并不是说对所有的属性都适合这条规则。同样的代码,我们把title属性替换成value属性。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<input type="text" id="name" value="张三" />
<script>
var input = document.getElementById('name');
//input.value='李四';
input.setAttribute('value', '李四');
var name1 = input.getAttribute('value');
var name2 = input.value;
document.write(name1 + "," + name2);
</script>
</body>
</html>
运行结果表明,如果通过点语法改变value的值,则通过getAttribute('value')获得的值并没有因此改变;而如果通过setAttribute改变value的值,则通过点语法读取的value值会随之改变。可见此处的value属性有些特殊。
再看如下代码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script>
function f()
{
var input = document.getElementById('name');
var name1 = input.getAttribute('value');
var name2 = input.value;
alert(name1 + "," + name2);
}
</script>
</head>
<body>
<input type="text" id="name" value="张三" />
<input type="button" id="button" value="测试" οnclick="f();" />
</body>
</html>
页面显示后,我们在浏览器中手动修改输入框表单中的值为”李四“,如下所示:
可见,虽然我们修改了控件的值,但是通过getAttribute('value')读取到的属性值并没有改变,而通过input.value读取的值却实时反映了这种变化。其实这个例子中我们手动修改文本框的值和通过input.value="李四”的效果一样,都是没有改变名为value的属性值。
我们来看看HTMLInputElement的定义:
interface HTMLInputElement : HTMLElement { attribute DOMString defaultValue; attribute boolean defaultChecked; readonly attribute HTMLFormElement form; attribute DOMString accept; attribute DOMString accessKey; attribute DOMString align; attribute DOMString alt; attribute boolean checked; attribute boolean disabled; attribute long maxLength; attribute DOMString name; attribute boolean readOnly; attribute DOMString size; attribute DOMString src; attribute long tabIndex; readonly attribute DOMString type; attribute DOMString useMap; attribute DOMString value; //这就是那个特殊的value属性 void blur(); void focus(); void select(); void click(); };
哪位高人能解释为什么这个名为value的属性会出现这种特殊的情况?????
与此相关的问题是,DOM也不能通过style属性读取元素来自外部css文件的配置信息。
另外,html标签中的属性名字和DOM定义属性名字也有不一致的时候,如HTML中的class属性,由于是js的保留字,所以不能直接使用,DOM将其对应到了className这个名字。