注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

星期五

明天不上班

 
 
 

日志

 
 
关于我

一个特立独行的Java程序员,比较宅,上上网,写博客,听音乐,看电影。

网易考拉推荐

2011年9月9日  

2010-12-08 19:14:05|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

节点是……

用最简单的话说,DOM 树中的任何事物都是节点 。之所以用 “事物” 这个模糊的字眼,是因为只能明确到这个程度。比如 HTML 中的元素(如 img)和 HTML 中的文本片段(如 “Scroll down for more details”)没有多少明显的相似之处。但这是因为您考虑的可能是每种类型的功能,关注的是它们的不同点。

但是如果从另一个角度观察,DOM 树中的每个元素和每段文本都有一个父亲,这个父节点可能是另一个元素(比如嵌套在 p 元素中的 img)的孩子,或者 DOM 树中的顶层元素(这是每个文档中都出现一次的特殊情况,即使用 html 元素的地方)。另外,元素和文本都有一个类型。显然,元素的类型就是元素,文本的类型就是文本。每个节点还有某种定义明确的结构:下面还有节点(如子元素)吗?有兄弟节点(与元素或文本 “相邻的” 节点)吗?每个节点属于哪个文档?

显然,大部分内容听起来很抽象。实际上,说一个元素的类型是元素似乎有点冒傻气。但是要真正认识到将节点作为通用对象类型的价值,必须抽象一点来思考。

通用节点类型

DOM 代码中最常用的任务就是在页面的 DOM 树中导航。比方说,可以通过其 “id” 属性定位一个 form,然后开始处理那个 form 中内嵌的元素和文本。其中可能包含文字说明、输入字段的标签、真正的 input 元素,以及其他 HTML 元素(如 img)和链接(a 元素)。如果元素和文本是完全不同的类型,就必须为每种类型编写完全不同的代码。

如果使用一种通用节点类型情况就不同了。这时候只需要从一个节点移动到另一个节点,只有当需要对元素或文本作某种特殊处理时才需要考虑节点的类型。如果仅仅在 DOM 树中移动,就可以与其他节点类型一样用同样的操作移动到元素的父节点或者子节点。只有当需要某种节点类型的特殊性质时,如元素的属性,才需要对节点类型作专门处理。将 DOM 树中的所有对象都看作节点可以简化操作。记住这一点之后,接下来我们将具体看看 DOM 节点构造应该提供什么,首先从属性和方法开始。

节点的属性

使用 DOM 节点时需要一些属性和方法,因此我们首先来讨论节点的属性和方法。DOM 节点的属性主要有:

  • nodeName 报告节点的名称(详见下述)。
  • nodeValue 提供节点的 “值”(详见后述)。
  • parentNode 返回节点的父节点。记住,每个元素、属性和文本都有一个父节点。
  • childNodes 是节点的孩子节点列表。对于 HTML,该列表仅对元素有意义,文本节点和属性节点都没有孩子。
  • firstChild 仅仅是 childNodes 列表中第一个节点的快捷方式。
  • lastChild 是另一种快捷方式,表示 childNodes 列表中的最后一个节点。
  • previousSibling 返回当前节点之前 的节点。换句话说,它返回当前节点的父节点的 childNodes 列表中位于该节点前面的那个节点(如果感到迷惑,重新读前面一句)。
  • nextSibling 类似于 previousSibling 属性,返回父节点的 childNodes 列表中的下一个节点。
  • attributes 仅用于元素节点,返回元素的属性列表。

其他少数几种属性实际上仅用于更一般的 XML 文档,在处理基于 HTML 的网页时没有多少用处。

不常用的属性

上述大部分属性的意义都很明确,除了 nodeNamenodeValue 属性以外。我们不是简单地解释这两个属性,而是提出两个奇怪的问题:文本节点的 nodeName 应该是什么?类似地,元素的 nodeValue 应该是什么

如果这些问题难住了您,那么您就已经了解了这些属性固有的含糊性。nodeNamenodeValue 实际上并非适用于所有 节点类型(节点的其他少数几个属性也是如此)。这就说明了一个重要概念:任何这些属性都可能返回空值(有时候在 JavaScript 中称为 “未定义”)。比方说,文本节点的 nodeName 属性是空值(或者在一些浏览器中称为 “未定义”),因为文本节点没有名称。如您所料,nodeValue 返回节点的文本。

类似地,元素有 nodeName,即元素名,但元素的 nodeValue 属性值总是空。属性同时具有 nodeNamenodeValue。下一节我还将讨论这些单独的类型,但是因为这些属性是每个节点的一部分,因此在这里有必要提一提。

现在看看 清单 1,它用到了一些节点属性。


清单 1. 使用 DOM 中的节点属性

     // These first two lines get the DOM tree for the current Web page,      //    and then the <html> element for that DOM tree     var myDocument = document;     var htmlElement = myDocument.documentElement;     // What's the name of the <html> element? "html"     alert("The root element of the page is " + htmlElement.nodeName);     // Look for the <head> element     var headElement = htmlElement.getElementsByTagName("head")[0];     if (headElement != null) {       alert("We found the head element, named " + headElement.nodeName);       // Print out the title of the page       var titleElement = headElement.getElementsByTagName("title")[0];       if (titleElement != null) {         // The text will be the first child node of the <title> element         var titleText = titleElement.firstChild;         // We can get the text of the text node with nodeValue         alert("The page title is '" + titleText.nodeValue + "'");       }       // After <head> is <body>       var bodyElement = headElement.nextSibling;       while (bodyElement.nodeName.toLowerCase() != "body") {         bodyElement = bodyElement.nextSibling;       }       // We found the <body> element...      // We'll do more when we know some methods on the nodes.     }

节点方法

接下来看看所有节点都具有的方法(与节点属性一样,我省略了实际上不适用于多数 HTML DOM 操作的少数方法):

  • insertBefore(newChild, referenceNode)newChild 节点插入到 referenceNode 之前。记住,应该对 newChild 的目标父节点调用该方法。
  • replaceChild(newChild, oldChild)newChild 节点替换 oldChild 节点。
  • removeChild(oldChild) 从运行该方法的节点中删除 oldChild 节点。
  • appendChild(newChild)newChild 添加到运行该函数的节点之中。newChild 被添加到目标节点孩子列表中的末端
  • hasChildNodes() 在调用该方法的节点有孩子时则返回 true,否则返回 false。
  • hasAttributes() 在调用该方法的节点有属性时则返回 true,否则返回 false。

注意,大部分情况下所有这些方法处理的都是节点的孩子。这是它们的主要用途。如果仅仅想获取文本节点值或者元素名,则不需要调用这些方法,使用节点属性就可以了。清单 2清单 1 的基础上增加了方法使用。


清单 2. 使用 DOM 中的节点方法

// These first two lines get the DOM tree for the current Web page,      //    and then the <html> element for that DOM tree     var myDocument = document;     var htmlElement = myDocument.documentElement;     // What's the name of the <html> element? "html"     alert("The root element of the page is " + htmlElement.nodeName);     // Look for the <head> element     var headElement = htmlElement.getElementsByTagName("head")[0];     if (headElement != null) {       alert("We found the head element, named " + headElement.nodeName);       // Print out the title of the page       var titleElement = headElement.getElementsByTagName("title")[0];       if (titleElement != null) {         // The text will be the first child node of the <title> element         var titleText = titleElement.firstChild;         // We can get the text of the text node with nodeValue         alert("The page title is '" + titleText.nodeValue + "'");       }       // After <head> is <body>       var bodyElement = headElement.nextSibling;       while (bodyElement.nodeName.toLowerCase() != "body") {         bodyElement = bodyElement.nextSibling;       }       // We found the <body> element...       // Remove all the top-level <img> elements in the body       if (bodyElement.hasChildNodes()) {         for (i=0; i<bodyElement.childNodes.length; i++) {           var currentNode = bodyElement.childNodes[i];           if (currentNode.nodeName.toLowerCase() == "img") {            bodyElement.removeChild(currentNode);           }         }       }     }

测试一下!

目前虽然只看到了两个例子,清单 12,不过通过这两个例子您应该能够了解使用 DOM 树能够做什么。如果要尝试一下这些代码,只需要将 清单 3 拖入一个 HTML 文件并保存,然后用 Web 浏览器打开。


清单 3. 包含使用 DOM 的 JavaScript 代码的 HTML 文件

<html> <head>   <title>JavaScript and the DOM</title>   <script language="JavaScript">    function test() {     // These first two lines get the DOM tree for the current Web page,     //    and then the <html> element for that DOM tree     var myDocument = document;     var htmlElement = myDocument.documentElement;     // What's the name of the <html> element? "html"     alert("The root element of the page is " + htmlElement.nodeName);     // Look for the <head> element     var headElement = htmlElement.getElementsByTagName("head")[0];     if (headElement != null) {       alert("We found the head element, named " + headElement.nodeName);       // Print out the title of the page       var titleElement = headElement.getElementsByTagName("title")[0];       if (titleElement != null) {         // The text will be the first child node of the <title> element         var titleText = titleElement.firstChild;         // We can get the text of the text node with nodeValue         alert("The page title is '" + titleText.nodeValue + "'");       }       // After <head> is <body>       var bodyElement = headElement.nextSibling;       while (bodyElement.nodeName.toLowerCase() != "body") {         bodyElement = bodyElement.nextSibling;       }       // We found the <body> element...       // Remove all the top-level <img> elements in the body       if (bodyElement.hasChildNodes()) {         for (i=0; i<bodyElement.childNodes.length; i++) {           var currentNode = bodyElement.childNodes[i];           if (currentNode.nodeName.toLowerCase() == "img") {             bodyElement.removeChild(currentNode);           }         }       }     }   }   </script> </head> <body>   <p>JavaScript and DOM are a perfect match.       You can read more in <i>Head Rush Ajax</i>.</p>   <img src="http://www.headfirstlabs.com/Images/hraj_cover-150.jpg" />   <input type="button" value="Test me!" onClick="test();" /> </body></html>

将该页面加载到浏览器后,可以看到类似 图 1 所示的画面。


图 1. 用按钮运行 JavaScript 的 HTML 页面
用按钮运行 JavaScript 的 HTML 页面

单击 Test me! 将看到 图 2 所示的警告框。


图 2. 使用 nodeValue 显示元素名的警告框
使用 nodeValue 显示元素名的警告框

代码运行完成后,图片将从页面中实时删除,如 图 3 所示。


图 3. 使用 JavaScript 实时删除图像
使用 JavaScript 实时删除图像

API 设计问题

再看一看各种节点提供的属性和方法。对于那些熟悉面向对象(OO)编程的人来说,它们说明了 DOM 的一个重要特点:DOM 并非完全面向对象的 API。首先,很多情况下要直接使用对象的属性而不是调用节点对象的方法。比方说,没有 getNodeName() 方法,而要直接使用 nodeName 属性。因此节点对象(以及其他 DOM 对象)通过属性而不是函数公开了大量数据。

其次,如果习惯于使用重载对象和面向对象的 API,特别是 Java 和 C++ 这样的语言,就会发现 DOM 中的对象和方法命名有点奇怪。DOM 必须能用于 C、Java 和 JavaScript(这只是其中的几种语言),因此 API 设计作了一些折衷。比如,NamedNodeMap 方法有两种不同的形式:

  • getNamedItem(String name)
  • getNamedItemNS(Node node)

对于 OO 程序员来说这看起来非常奇怪。两个方法目的相同,只不过一个使用 String 参数而另一个使用 Node 参数。多数 OO API 中对这两种版本都会使用相同的方法名。运行代码的虚拟机将根据传递给方法的对象类型决定运行哪个方法。

问题在于 JavaScript 不支持这种称为方法重载 的技术。换句话说,JavaScript 要求每个方法或函数使用不同的名称。因此,如果有了一个名为 getNamedItem() 的接受字符串参数的方法,就不能再有另一个方法或函数也命名为 getNamedItem(),即使这个方法的参数类型不同(或者完全不同的一组参数)。如果这样做,JavaScript 将报告错误,代码不会按照预期的方式执行。

从根本上说,DOM 有意识地避开了方法重载和其他 OO 编程技术。这是为了保证该 API 能够用于多种语言,包括那些不支持 OO 编程技术的语言。后果不过是要求您多记住一些方法名而已。好处是可以在任何语言中学习 DOM,比如 Java,并清楚同样的方法名和编码结构也能用于具有 DOM 实现的其他语言,如 JavaScript。

  评论这张
 
阅读(183)| 评论(0)
推荐

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017