使用DOM操纵HTML文档几点粗浅认识

来源:互联网 发布:删除有条件的数据sql 编辑:程序博客网 时间:2024/05/16 10:17

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代码的输出结果如下:
0--节点类型:3, 节点名称:#text, 节点值: 我是文本节点 , 节点值长度:12
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

属性节点的类型代号为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="测试" onclick="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这个名字。