第十章:DOM(节点层次:Node类型和Document类型)

DOM详解
本文详细介绍了DOM(文档对象模型)的基础概念及其在HTML和XML文档中的应用。涵盖了Node类型、节点关系、文档操作方法等内容。

DOM

  • DOM(文档对象模型)是针对HTML和XML文档的一个API。DOM脱胎于Netscape及微软公司创造的DHTML(动态HTML)。
  • 1998年10月DOM 1级规范成为W3C的推荐标准,IE、Firefox、Safari、Chrome、Opera都非常完善地实现了DOM。不过IE中的DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM对象和原生JS对象的行为和活动特点存在差异。

节点层次

  • 文档节点是每个文档的根节点。文档节点的最外层元素称为文档元素。每个文档只能有一个文档元素。
    这里写图片描述

Node类型

  • DOM 1级定义了一个Node接口,这个接口在JavaScript(无接口)中是作为Node类型(除了IE其他浏览器都可以访问该类型)实现的。JavaScript中所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法。
nodeType
  • 每个节点都有一个nodeType属性,用于表名节点的类型。节点一共有12种类型:(括号内为值)

    1. Node.ELEMENT_NODE (1) 元素节点
    2. Node.ATTRIBUTE_NODE (2) 特性节点
    3. Node.TEXT_NODE (3) 文本节点
    4. Node.CDATA_SECTION_NODE (4) CDATA节点
    5. Node.ENTITY_REFERENCE_NODE (5) 实体引用节点
    6. Node.ENTITY_NODE (6) 实体节点
    7. Node.PROCESSING_INSTRUCTION_NODE (7) 处理指令节点
    8. Node.COMMENT_NODE (8) 注释节点
    9. Node.DOCUMENT_NODE (9) 文档节点
    10. Node.DOCUMENT_TYPE_NODE (10) 文档类型节点
    11. Node.DOCUMENT_FRAGMENT_NODE (11) 文档片段节点
    12. Node.NOTATION_NODE (12) 符号节点
  • 通过比较上面的常量,可以很容易确定节点的类型。

    if (someNode.nodeType == Node.ELEMENT_NODE) {
        alert("Node is an element");
    }
    //因为IE没有公开Node类型的构造函数。所以上面代码对IE无效,改成下面代码即可。
    if (someNode.nodeType == 1) {
        alert("Node is an element");
    }
nodeName和nodeValue属性
  • nodeName一般是标签名,而nodeValue一般是内部的值。但是这两个属性的值完全取决于节点的类型。
<!DOCTYPE html>
<html>
    <title>Example</title>
</head>
<body>
    <a href="#">hello</a>
    <script type="text/javascript">
        var node_a = document.getElementsByTagName("a")[0];
        var node_text = node_a.lastChild;
        alert(node_a.nodeType);//1
        alert(node_a.nodeName);//A
        alert(node_a.nodeValue);//null

        alert(node_text.nodeType);//3
        alert(node_text.nodeName);//#text
        alert(node_text.nodeValue);//hello
    </script>
</body>
</html>
  • 由上面的例子可以看出。对于元素节点,nodeName是元素标签名(大写),nodeValue为null。注意是null而不是undefined。因为这里想表明的意思是元素节点的nodeValue为空,而不是没有定义。对于文本节点,nodeName是一个固定值(#text),nodeValue为文本内容。
节点关系
  • 由于是树的结构,那么节点之间就会存在各种关系。例如兄弟节点,孩子节点,父亲节点。一个节点可以有很多兄弟节点,这些节点都是位于同一个父亲节点下的,并且他们保持一定的顺序。而孩子节点自然是该节点下的节点组。
childNodes
  • 每个节点都有一个childNodes属性,其中保存着一个NodeList对象。NodeList是一个类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。这里注意NodeList对象并不是Array的实例,尽管他们非常像。NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。
  • 我们可以使用”[]”去访问NodeList对象中的节点,也可以使用item()方法。
    var firstChild = someNode.childNodes[0];//比较常用
    var secondChild = someNode.childNodes.item(1);
    var count= someNode.childNodes.length;
    //判断是否有子节点
    someNode.childNodes.length > 0;
    someNode.hasChildNodes(); //意义同上
parentNode
  • 该属性指向父节点。
previousSibling 和 nextSibling
  • 兄弟节点之间的前后。列表中第一个节点的previousSibling属性为null,而列表中最后一个节点的nextSibling为null。
firstChild 和 lastChild
  • 第一个子节点以及最后一个子节点。如果没有子节点firstChild和lastChild均为null。
  • 总结如下图:

这里写图片描述

ownerDocument
  • 该属性指向表示整个文档的文档节点。通过这个属性,我们可以不必在节点层次通过层层回溯到达顶端,而是可以直接访问文档节点。
操作节点
 appendChild()
  • 用于在childNodes列表末尾添加一个节点。添加完成后,childNodes的新增节点,父节点及以前的最后一个子节点的关系指针都会相应地得到更新。该方法返回新增节点:
    var retrunNode = someNode.appendChild(newNode);
    alert(retrunNode == newNode);//true
    alert(someNode.lastChild == newNode);//true
  • 需要注意的是,如果传入appendChild()中的节点已经是文档中的一部分了,那么结果就是将该节点从原来的位置转移到新位置。因为同一个DOM节点不可能同时出现在文档中的多个位置上。
<!DOCTYPE html>
<html>
    <title>Example</title>
</head>
<body>
    <test href="#"><h>1</h><h>2</h><h>3</h><h>4</h><h>5</h></test>
    <script type="text/javascript">
        var node = document.getElementsByTagName("test")[0];
        var node1 = node.firstChild;
        var node2 = node.childNodes[1];
        var retrunNode = node.appendChild(node.firstChild);
        alert(node1 == retrunNode);//true
        alert(node.firstChild == retrunNode);//false
        alert(node.lastChild == retrunNode);//true
        alert(node.firstChild == node2);//true 此时原来的第二个节点成为firstChild
        alert(node2.previousSibling); //null 因为node2只是引用,指向的对象的previousSibling在appendChild操作时已经更新了
        alert(node.firstChild.previousSibling);//null
    </script>
</body>
</html>
insertBefore()
  • 该方法必须传2个值,一个是插入的节点,一个是参照节点。如果参照节点为null,则插入到末尾。由于没有insertAfter()方法,这里我自定义一个:
    //不能在IE上使用
    Node.prototype.insertAfter = function(node, afterer) {
        if (!afterer) {
            this.appendChild(node);
        } else {
            for (var i=0; i<this.childNodes.length; i++) {
                if (this.childNodes[i] == afterer) {
                    this.insertBefore(node, this.childNodes[i].nextSibling);
                }
            }
        }
    }
 replaceChild()和removeChild()
  • replaceChild()传入两个参数,是removeChild()的升级版。参数顺序和insertBefore一致。可以想象成先是在该节点前插入新节点,然后再removeChild()参照节点。removeChild()只传入一个参数,即要删除的节点。这两个方法都会返回被移除的节点的指针。被移除的节点仍然为文档所有,只是在文档中没有了自己的位置。
其他方法
cloneNode()
  • 该方法接收一个布尔值参数,表示是否执行深复制。在参数为true的情况下,执行深复制,也就是复制节点以及整个子节点树。如果为false,则执行浅复制,就是只复制节点本身。返回后的节点副本属于文档所有,但没有父节点。所有我们还得通过操作节点方法将他添加入文档中。
  • cloneNode()方法不会复制添加到DOM节点中的JavaScript属性(比如onclick),这个方法只复制特性、子节点。不过IE在此存在一个BUG,即它也会复制事件处理程序(比如onclick)。
normalize()
  • 这个方法的作用是处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除。如果找到接连出现两个文本节点,就合并。

Document类型

  • JavaScript通过Document类型表示文档。在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例。document对象是window对象的一个属性,因此可以作为全局对象来访问。Document节点具有以下特征:
    1. nodeType的值为9
    2. nodeName的值为”#document”
    3. nodeValue的值为null
    4. parentNode的值为null
    5. ownerDocument的值为null
    6. 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment
文档的子节点
  • documentElement属性始终指向HTML页面的<html>元素。childNodes列表访问文档元素。下面是一个例子:
    <html>
        <body>
            <script>
                var html = document.documentElement;//取得<html>的引用
                alert(html == document.childNodes[0]);//true 如果存在doctype 那就是false
                alert(html == document.firstChild);//true
            </script>
        </body>
    </html>
  • body属性则直接指向<body>元素。如 var body = document.body;
  • Document除了上面的Element子节点,还有一个可能的节点是DocumentType。通常将标签看成一个与文档其他部分不同的实体,可以通过document.doctype来访问它的信息。
  • 浏览器对document.doctype的支持差别很大,下面是总结。

    1. IE8及之前版本:如果存在文档类型声明,会被当成注释(Comment节点),而document.doctype的值始终为null。
    2. IE9+及Firefox:如果存在文档类型声明,则将其作为文档的第一个子节点。可以通过childNodes[0]或者firstChild获得(而我的Chrome也是这样)。
    3. Safari、Chrome和Opera:如果存在文档类型声明,不会被当成文档的子节点(而我的Chrome却是第二种情况)。
  • 以上的差别以及不确定性说明该属性的用处也有限。另外不同浏览器对于<html>外的注释节点的处理也大有不同,所以最好不要有以下这样的写法。

    <!-- 注释 -->
    <html></html>
    <!-- 注释 -->
  • 多数情况下,我们都用不着在document上调用appendChild(),removeChild(),replaceChild()方法,因为文档类型是只读的,而且它只能有一个元素子节点。
文档信息
  • 作为HTMLDocument的一个实例,document对象还有一些标准的Document对象所没有的属性。这些属性提供了document对象所表现的网页的一些信息。其中第一个属性就是title,包含这<title>元素中的文本。
<!DOCTYPE >
<html>
<head>
    <title>test</title>
</head>
<body>
<script>
    alert(document.getElementsByTagName("title")[0].innerHTML);//test
    var title = document.title;
    alert(title);//test
    document.title = "new name";//并且能改变标题名
    alert(document.getElementsByTagName("title")[0].innerHTML);//new name
</script>
</body>
</html>
  • 接下来的三个属性都与网页的请求有关。URLdomainreferrer。URL属性(注意大写)包含页面完整的URL,domain属性中包含页面的域名,而referrer属性中则保存着链接到当前页面的那个页面的URL(可能为空字符串)。其实所有这些信息都存在于请求的HTTP头部,只不过我们可以通过这些属性在JS中去访问它们。
  • 当页面中包含其他子域的框架或内嵌框架,能够设置document.domain就很方便了。由于跨域安全限制,来自不同子域的页面无法通过JS通信。而通过设置每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JS对象了。不过修改domain有一个限制,比如说原来是”www.baidu.com”你就只能修改成”baidu.com”,不能将这个属性设置为URL中不包含的域。书上说,你改成更加松散的”baidu.com”,就不能改回”www.baidu.com”了,但是我通过Chrome测试了后发现,可以改回来。
 查找元素
  • 这里要讲一下两个方法:document.getElementByIddocument.getElementsByTagName。第一个方法要传入标签的id属性,并且大小写要一致(IE8部分版本、IE7及以前不区分大小写,所以id最好无论大小写都要唯一),找到唯一的一个id元素(这样说明了在一个文档中id必须是唯一的),如果没有找到则返回null。那如果有id相同的标签怎么办?我在Chrome中测试是返回第一个,我想document.getElementById的内部实现机制也应该是从上往下去遍历节点的id,找到了就返回吧(没必要继续搜索下去,该方法只返回一个节点)。
  • IE7及较低版本还有一个怪癖:如果表单元素(<input>、<textarea> 、<button>、<select>)的name特性与给定ID匹配,则该元素会被返回。
    <input type="text" name="myElement" value="text">
    <div id="myElement">A div</div>
  • 在IE7中对上面的代码调用document.getElementById(“myElement”),会返回input元素。所以在取name的时候也不要和其他标签的id相同。

  • getElementsByTagName()方法接收一个参数,即要取得元素的标签名,而返回的是包含零个或多个元素的NodeList。在HTML文档中,这个方法会返回HTMLCollection对象,作为一个”动态”集合,改对象和NodeList对象类似。对于该对象可以使用方括号或item()方法来访问项,length属性是元素的数量。

  • HTMLCollection对象还有一个方法,叫nameItem(),使用这个方法可以通过元素的name特性取得集合中的项。此外也可以通过方括号(name)获得项。
    var imgs = document.getElementsByTagName("img");
    alert(imgs.length);
    alert(imgs[0].src);
    alert(imgs.item(0).src);
    var myImage = imgs.nameItem("myImage");
    //var myImage = imgs["myImage"];
  • 对HTMLCollection而言,我们可以向方括号中传入数值或字符串形式的索引值。在后台,对数值索引就会调用item(),对字符串索引就会调用nameItem()。
  • 想要获得文档中的所有元素,可以向getElementsByTagName()传入”*”。在JS及CSS中,星号通常表示全部。

  • 还有一个方法是HTMLDocument类型才有的方法,是getElementsByName()。该方法会返回带有给定name特性的所有元素。值得一提的是,该方法也会返回HTMLCollection对象,对其调用nameItem(),会始终返回第一个元素。

特殊集合
  • 除了属性和方法,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象,包括:

    1. document.anchors,包含文档中所有带name特性的<a>元素。
    2. document.applets,包含文档中所有的<applet>,因为不再推荐<applet>元素,所有这个集合已经不建议使用了。
    3. document.forms,包含所有的<form>元素。
    4. document.images,包含所有的<img>元素。
    5. document.links,包含文档中所有带href特性的<a>元素。
  • 因为他们是HTMLCollection对象,所以集合中的项也会随着当前文档内容的更新而更新。

文档写入
  • write(),writeln(),open(),close()。四个方法具有将输出流写入到网页中的能力。而open和close分别用于打开和关闭网页的输出流。如果是在页面加载期间使用write()和writeln()方法,则不需要这2个方法。
    document.write("<strong>" + (new Date()).toString() + "</strong>");//动态加入节点
    document.write("<script type=\"text\javascript\" src=\"file.js\">" + "<\/script>");//动态引入js文件,注意"<\/script>"
    //如果在页面加载完成后再调用write则会重写整个页面。
    window.onload = function(){
        document.write("Hello world!");
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值