操纵 DOM

本文深入讲解了DOM的基本概念,包括节点、属性、元素和文本节点等,并通过实例演示了如何利用DOM API创建、查找和替换网页元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

操纵 DOM

 

节点的概念

节点是 DOM 中最基本的对象类型。实际上,您将在本文中看到,基本上 DOM 定义的其他所有对象都是节点对象的扩展。但是在深入分析语义之前,必须了解节点所代表的概念,然后再学习节点的具体属性和方法就非常简单了。

DOM 树中,基本上一切都是节点。每个元素在最底层上都是 DOM 树中的节点。每个属性都是节点。每段文本都是节点。甚至注释、特殊字符(如版权符号 ©)、DOCTYPE 声明(如果 HTML 或者 XHTML 中有的话)全都是节点。因此在讨论这些具体的类型之前必须清楚地把握什么是节点。

节点是……

用最简单的话说,节点就是 DMO 树中的任何事物。之所以用事物这个模糊的字眼,是因为只能明确到这个程度。比如 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 节点构造应该提供什么,首先从属性和方法开始。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

节点的属性

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

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

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

不常用的属性

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

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

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

现在看看 清单 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.

    }

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

节点方法

接下来看看所有节点都具有的方法(与节点属性一样,我省略了实际上不适用于多数 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);

          }

        }

      }

    }

 

测试一下!

目前虽然只看到了两个例子,清单 1 2,不过通过这两个例子您应该能够了解使用 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>

 

 

API 设计问题

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

其次,如果习惯于使用重载对象和面向对象的 API,特别是 Java C++ 这样的语言,就会发现 DOM 中的对象和方法命名有点奇怪。DOM 必须能用于 CJava 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

让程序员小心谨慎

如果深入研究 API 设计或者仅仅非常关注 API 设计,您可能会问:为何节点类型的属性不能适用于所有节点?这是一个很好的问题,答案和政治以及决策制定而非技术原因关系更密切。简单地说,答案就是,谁知道!但有点令人恼火,不是吗?

属性 nodeName 意味着允许每种类型的节点都有一个名字,但是很多情况下名字要么未定义,要么是对于程序员没有意义的内部名(比如在 Java 中,很多情况下文本节点的 nodeName 被报告为 “#text”)。从根本上说,必须假设您得自己来处理错误。直接访问 myNode.nodeName 然后使用该值是危险的,很多情况下这个值为空。因此与通常的编程一样,程序员要谨慎从事。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

通用节点类型

现在已经介绍了 DOM 节点的一些特性和属性(以及一些奇特的地方),下面开始讲述您将用到的一些特殊节点类型。多数 Web 应用程序中只用到四种节点类型:

  • 文档节点表示整个 HTML 文档。
  • 元素节点表示 HTML 元素,如 a img
  • 属性节点表示 HTML 元素的属性,如 hrefa 元素)或 srcimg 元素)。
  • 文本节点表示 HTML 文档中的文本,如 “Click on the link below for a complete set list”。这是出现在 pa h2 这些元素中的文字。

处理 HTML 时,95% 的时间是跟这些节点类型打交道。因此本文的其余部分将详细讨论这些节点。(将来讨论 XML 的时候将介绍其他一些节点类型。)

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

文档节点

基本上所有基于 DOM 的代码中都要用到的第一个节点类型是文档节点。文档节点 实际上并不是 HTML(或 XML)页面中的一个元素而是页面本身。因此在 HTML Web 页面中,文档节点就是整个 DOM 树。在 JavaScript 中,可以使用关键字 document 访问文档节点:

// 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;

 

JavaScript 中的 document 关键字返回当前网页的 DOM 树。从这里可以开始处理树中的所有节点。

也可使用 document 对象创建新节点,如下所示:

  • createElement(elementName) 使用给定的名称创建一个元素。
  • createTextNode(text) 使用提供的文本创建一个新的文本节点。
  • createAttribute(attributeName) 用提供的名称创建一个新属性。

这里的关键在于这些方法创建节点,但是并没有将其附加或者插入到特定的文档中。因此,必须使用前面所述的方法如 insertBefore() appendChild() 来完成这一步。因此,可使用下面的代码创建新元素并将其添加到文档中:

var pElement = myDocument.createElement("p");

var text = myDocument.createTextNode("Here's some text in a p element.");

pElement.appendChild(text);

bodyElement.appendChild(pElement);

 

一旦使用 document 元素获得对 Web 页面 DOM 树的访问,就可以直接使用元素、属性和文本了。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

元素节点

虽然会大量使用元素节点,但很多需要对元素执行的操作都是所有节点共有的方法和属性,而不是元素特有的方法和属性。元素只有两组专有的方法:

  1. 与属性处理有关的方法
    • getAttribute(name) 返回名为 name 的属性值。
    • removeAttribute(name) 删除名为 name 的属性。
    • setAttribute(name, value) 创建一个名为 name 的属性并将其值设为 value
    • getAttributeNode(name) 返回名为 name 的属性节点(属性节点在 下一节 介绍)。
    • removeAttributeNode(node) 删除与指定节点匹配的属性节点。
  2. 与查找嵌套元素有关的方法
    • getElementsByTagName(elementName) 返回具有指定名称的元素节点列表。

这些方法意义都很清楚,但还是来看几个例子吧。

处理属性

处理元素很简单,比如可用 document 对象和上述方法创建一个新的 img 元素:

var imgElement = document.createElement("img");

imgElement.setAttribute("src", "http://www.headfirstlabs.com/Images/hraj_cover-150.jpg");

imgElement.setAttribute("width", "130");

imgElement.setAttribute("height", "150");

bodyElement.appendChild(imgElement);

 

现在看起来应该非常简单了。实际上,只要理解了节点的概念并知道有哪些方法可用,就会发现在 Web 页面和 JavaScript 代码中处理 DOM 非常简单。在上述代码中,JavaScript 创建了一个新的 img 元素,设置了一些属性然后添加到 HTML 页面的 body 元素中。

查找嵌套元素

发现嵌套的元素很容易。比如,下面的代码用于发现和删除 清单 3 所示 HTML 页面中的所有 img 元素:

      // 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);

          }

        }

      }

 

也可以使用 getElementsByTagName() 完成类似的功能:

     

// Remove all the top-level <img> elements in the body

      var imgElements = bodyElement.getElementsByTagName("img");

      for (i=0; i<imgElements.length; i++) {

        var imgElement = imgElements.item[i];

        bodyElement.removeChild(imgElement);

      }

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

属性节点

DOM 将属性表示成节点,可以通过元素的 attributes 来访问元素的属性,如下所示:

     

// Remove all the top-level <img> elements in the body

      var imgElements = bodyElement.getElementsByTagName("img");

      for (i=0; i<imgElements.length; i++) {

        var imgElement = imgElements.item[i];

        // Print out some information about this element

        var msg = "Found an img element!";

        var atts = imgElement.attributes;

        for (j=0; j<atts.length; j++) {

          var att = atts.item(j);

          msg = msg + "/n  " + att.nodeName + ": '" + att.nodeValue + "'";

        }

        alert(msg);

        bodyElement.removeChild(imgElement);

      }

 

<!--[if !vml]--><!--[endif]-->

属性的奇特之处

对于 DOM 来说属性有一些特殊的地方。一方面,属性实际上并不像其他元素或文本那样是元素的孩子,换句话说,属性并不出现在元素之下。同时,属性显然和元素有一定的关系,元素拥有属性。DOM 使用节点表示属性,并允许通过元素的专门列表来访问属性。因此属性是 DOM 树的一部分,但通常不出现在树中。有理由说,属性和 DOM 树结构其他部分之间的关系有点模糊。

需要指出的是,attributes 属性实际上是对节点类型而非局限于元素类型来说的。有点古怪,不影响您编写代码,但是仍然有必要知道这一点。

虽然也能使用属性节点,但通常使用元素类的方法处理属性更简单。其中包括:

  • getAttribute(name) 返回名为 name 的属性值。
  • removeAttribute(name) 删除名为 name 的属性。
  • setAttribute(name, value) 创建一个名为 name 的属性并将其值设为 value

这三个方法不需要直接处理属性节点。但允许使用简单的字符串属性设置和删除属性及其值。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

文本节点

需要考虑的最后一种节点是文本节点(至少在处理 HTML DOM 树的时候如此)。基本上通常用于处理文本节点的所有属性都属于节点对象。实际上,一般使用 nodeValue 属性来访问文本节点的文本,如下所示:

var pElements = bodyElement.getElementsByTagName("p");

for (i=0; i<pElements.length; i++) {

  var pElement = pElements.item(i);

  var text = pElement.firstChild.nodeValue;

  alert(text);

}

 

少数其他几种方法是专门用于文本节点的。这些方法用于增加或分解节点中的数据:

  • appendData(text) 将提供的文本追加到文本节点的已有内容之后。
  • insertData(position, text) 允许在文本节点的中间插入数据。在指定的位置插入提供的文本。
  • replaceData(position, length, text) 从指定位置开始删除指定长度的字符,用提供的文本代替删除的文本。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

什么节点类型?

到目前为止看到的多数代码都假设已经知道处理的节点是什么类型,但情况并非总是如此。比方说,如果在 DOM 树中导航并处理一般的节点类型,可能就不知道您遇到了元素还是文本。也许获得了 p 元素的所有孩子,但是不能确定处理的是文本、b 元素还是 img 元素。这种情况下,在进一步的处理之前需要确定是什么类型的节点。

所幸的是很容易就能做到。DOM 节点类型定义了一些常量,比如:

  1. Node.ELEMENT_NODE 是表示元素节点类型的常量。
  2. Node.ATTRIBUTE_NODE 是表示属性节点类型的常量。
  3. Node.TEXT_NODE 是表示文本节点类型的常量。
  4. Node.DOCUMENT_NODE 是表示文档节点类型的常量。

还有其他一些节点类型,但是对于 HTML 除了这四种以外很少用到。我有意没有给出这些常量的值,虽然 DOM 规范中定义了这些值,永远不要直接使用那些值,因为这正是常量的目的!

nodeType 属性

可使用 nodeType 属性比较节点和上述常量 —— 该属性定义在 DOM node 类型上因此可用于所有节点,如下所示:

var someNode = document.documentElement.firstChild;

if (someNode.nodeType == Node.ELEMENT_NODE) {

  alert("We've found an element node named " + someNode.nodeName);

} else if (someNode.nodeType == Node.TEXT_NODE) {

  alert("It's a text node; the text is " + someNode.nodeValue);

} else if (someNode.nodeType == Node.ATTRIBUTE_NODE) {

  alert("It's an attribute named " + someNode.nodeName

                        + " with a value of '" + someNode.nodeValue + "'");

}

 

这个例子非常简单,但说明了一个大问题:得到节点的类型非常 简单。更有挑战性的是知道节点的类型之后确定能做什么,只要掌握了节点、文本、属性和元素类型提供了什么属性和方法,就可以自己进行 DOM 编程了。

好了,快结束了。

实践中的挫折

nodeType 属性似乎是使用节点的一个入场卷 —— 允许确定要处理的节点类型然后编写处理该节点的代码。问题在于上述 Node 常量定义不能正确地用于 Internet Explorer。因此如果在代码中使用 Node.ELEMENT_NODENode.TEXT_NODE 或其他任何常量,Internet Explorer 都将返回如 图 4 所示的错误。


4. Internet Explorer 报告错误
<!--[if !vml]-->Internet Explorer 不支持 Node 构造<!--[endif]-->

任何时候在 JavaScript 中使用 Node 常量,Internet Explorer 都会报错。因为多数人仍然在使用 Internet Explorer,应该避免在代码中使用 Node.ELEMENT_NODE Node.TEXT_NODE 这样的构造。尽管据说即将发布的新版本 Internet Explorer 7.0 将解决这个问题,但是在 Internet Explorer 6.x 退出舞台之前仍然要很多年。因此应避免使用 Node,要想让您的 DOM 代码(和 Ajax 应用程序)能用于所有主要浏览器,这一点很重要。

 

本系列的上一篇文章中考察了文档对象模型(DOM)编程中涉及到的概念——Web 浏览器如何把网页看作一棵树,现在您应该理解了 DOM 中使用的编程结构。本期教程将把这些知识用于实践,建立一个简单的包含一些特殊效果的 Web 页面,所有这些都使用 JavaScript 操纵 DOM 来创建,不需要重新加载或者刷新页面。

前面两期文章已经详细介绍了文档对象模型或者 DOM,读者应该很清楚 DOM 是如何工作的了。(前两期 DOM 文章以及 Ajax 系列更早文章的链接请参阅参考资料。)本教程中将把这些知识用于实践。我们将开发一个简单的 Web 应用程序,其用户界面可根据用户动作改变,当然要使用 DOM 来处理界面的改变。阅读完本文之后,就已经把学习到的关于 DOM 的技术和概念付诸应用了。

假设读者已经阅读过上两期文章,如果还没有的话,请先看一看,切实掌握什么是 DOM 以及 Web 浏览器如何将提供给它的 HTML CSS 转化成单个表示网页的树状结构。到目前为止我一直在讨论的所有 DOM 原理都将在本教程中用于创建一个能工作的(虽然有点简单)基于 DOM 的动态 Web 页面。如果遇到不懂的地方,可以随时停下来复习一下前面的两期文章然后再回来。

从一个示例应用程序开始

<!--[if !vml]--><!--[endif]-->

关于代码的说明

为了把注意力集中到 DOM JavaScript 代码上,我编写 HTML 的时候有些随意地采用内联样式(比如 h1 p 元素的 align 属性)。虽然对实验来说这样做是可接受的,但是对于开发的任何产品应用程序,我建议花点时间把所有的样式都放到外部 CSS 样式表中。

我们首先建立一个非常简单的应用程序,然后再添加一点 DOM 魔法。要记住,DOM 可以移动网页中的任何东西而不需要提交表单,因此足以和 Ajax 媲美;我们创建一个简单的网页,上面只显示一个普通的旧式大礼帽,还有一个标记为 Hocus Pocus! 的按钮(猜猜这是干什么的?)

初始 HTML

清单 1 显示了这个网页的 HTML。除了标题和表单外,只有一个简单的图片和可以点击的按钮。


清单 1. 示例应用程序的 HTML

 

<html>

 <head>

  <title>Magic Hat</title>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" />

   </p>

  </form>                                                                    

 </body>

</html>

 

可以在本文后面的下载中找到这段 HTML 和本文中用到的图片。不过我强烈建议您只下载那个图片,然后随着本文中逐渐建立这个应用程序自己动手输入代码。这样要比读读本文然后直接打开完成的应用程序能够更好地理解 DOM 代码。

查看示例网页

这里没有什么特别的窍门,打开网页可以看到图 1 所示的结果。


1. 难看的大礼帽
<!--[if !vml]-->难看的大礼帽<!--[endif]-->

关于 HTML 的补充说明

应该 注意的重要一点是,清单 1 图 1 中按钮的类型是 button 而不是提交按钮。如果使用提交按钮,单击该按钮将导致浏览器提交表单,当然表单没有 action 属性(完全是有意如此),从而会造成没有任何动作的无限循环。(应该自己试试,看看会发生什么。)通过使用一般输入按钮而不是提交按钮,可以把 javaScript 函数和它连接起来与浏览器交互而无需 提交表单。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

向示例应用程序添加元素

现在用一些 JavaScriptDOM 操作和小小的图片戏法装扮一下网页。

使用 getElementById() 函数

显然,魔法帽子没有兔子就没有看头了。这里首先用兔子的图片替换页面中原有的图片(再看看图 1),如图 2 所示。


2. 同样的礼帽,这一次有了兔子
<!--[if !vml]-->同样的礼帽,这一次有了兔子<!--[endif]-->

完成这个 DOM 小戏法的第一步是找到网页中表示 img 元素的 DOM 节点。一般来说,最简单的办法是用 getElementById() 方法,它属于代表 Web 页面的 document 对象。前面已经见到过这个方法,用法如下:

var elementNode = document.getElementById("id-of-element");

 

HTML 添加 id 属性

这是非常简单的 JavaScript,但是需要修改一下 HTML:为需要访问的元素增加 id 属性。也就是希望(用带兔子的新图片)替换的 img 元素,因此将 HTML 改为清单 2 的形式。


清单 2. 增加 id 属性

 

<html>

 <head>

  <title>Magic Hat</title>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" />

   </p>

  </form>                                                                    

 </body>

</html>

 

如果重新加载(或者打开)该页面,可以看到毫无变化,增加 id 属性对网页的外观没有影响。不过,该属性可以帮助 JavaScript DOM 更方便地处理元素。

抓住 img 元素

现在可以很容易地使用 getElementById() 了。已经有了需要元素的 ID,即 topHat,可以将其保存在一个新的 JavaScript 变量中。在 HTML 页面中增加清单 3 所示的代码。


清单 3. 访问 img 元素

 

<html>

 <head>

  <title>Magic Hat</title>

  <script language="JavaScript">

    function showRabbit() {

      var hatImage = document.getElementById("topHat");

    }

  </script>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" />

   </p>

  </form>                                                                     

 </body>

</html>

 

现在打开或重新加载该网页同样没有什么惊奇的地方。虽然现在能够访问图片,但是对它还什么也没有做。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

修改图片,麻烦的办法

完成所需修改有两种方法:一种简单,一种麻烦。和所有的好程序员一样,我也喜欢简单的办法;但是运用较麻烦的办法是一次很好的 DOM 练习,值得您花点时间。首先看看换图片比较麻烦的办法;后面再重新分析一下看看有没有更简单的办法。

用带兔子的新照片替换原有图片的办法如下:

  1. 创建新的 img 元素。
  2. 访问当前 img 元素的父元素,也就是它的容器。
  3. 在已有 img 元素之前 插入新的 img 元素作为该容器的子级。
  4. 删除原来的 img 元素。
  5. 结合起来以便在用户单击 Hocus Pocus! 按钮时调用刚刚创建的 JavaScript 函数。

创建新的 img 元素

通过上两期文章应该记住 DOM 中最关键的是 document 对象。它代表整个网页,提供了 getElementById() 这样功能强大的方法,还能够创建新的节点。现在要用到的就是这最后一种性质。

具体而言,需要创建一个新的 img 元素。要记住,在 DOM 中一切都是节点,但是节点被进一步划分为三种基本类型:

  • 元素
  • 属性
  • 文本节点

还有其他类型,但是这三种可以满足 99% 的编程需要。这里需要一个 img 类型的新元素。因此需要下列 JavaScript 代码:

var newImage = document.createElement("img");

 

这行代码可以创建一个 element 类型的新节点,元素名为 img。在 HTML 中基本上就是:

<img />

 

要记住,DOM 会创建结构良好的 HTML,就是说这个目前为空的元素包括起始和结束标签。剩下的就是向该元素增加内容或属性,然后将其插入到网页中。

对内容来说,img 是一个空元素。但是需要增加一个属性 src,它指定了要加载的图片。您也许认为要使用 addAttribute() 之类的方法,但情况并非如此。DOM 规范的制定者认为程序员可能喜欢简洁(的确如此!),因此他们规定了一个方法同时用于增加新属性和改变已有的属性值:setAttribute()

如果对已有的属性调用 setAttribute(),则把原来的值替换为指定的值。但是,如果调用 setAttribute() 并指定一个 存在的属性,DOM 就会使用提供的值增加一个属性。一个方法,两种用途!因此需要增加下列 JavaScript 代码:

var newImage = document.createElement("img");

newImage.setAttribute("src", "rabbit-hat.gif");

 

它创建一个图片元素然后设置适当的资源属性。现在,HTML 应该如清单 4 所示。


清单 4. 使用 DOM 创建新图片

 

<html>

 <head>

  <title>Magic Hat</title>

  <script language="JavaScript">

    function showRabbit() {

      var hatImage = document.getElementById("topHat");

      var newImage = document.createElement("img");

      newImage.setAttribute("src", "rabbit-hat.gif");

    }

  </script>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" />

   </p>

  </form>                                                                    

 </body>

</html>

 

可以加载该页面,但是不要期望有任何改变,因为目前所做的修改实际上还没有影响页面。另外,如果再看看任务列表中的第 5 ,就会发现还没有调用我们的 JavaScript 函数!

获得原始图片的父元素

现在有了要插入的图片,还需要找到插入的地方。但是不能将其插入到已有的图片中,而是要将其插入到已有图片之前然后再删除原来的图片。为此需要知道已有图片的父元素,实际上这就是插入和删除操作的真正关键所在。

应该记得,前面的文章中曾经指出 DOM 确实把网页看成一棵树,即节点的层次结构。每个节点都有父节点(树中更高层次的节点,该节点是它的一个子级),可能还有自己的子节点。对于图片来说,它没 有子级 —— 要记住图片是空元素,但是它肯定有父节点。甚至不需要知道父节点是什么,但是需要访问它。

为此,只要使用每个 DOM 节点都有的 parentNode 属性即可,比如:

var imgParent = hatImage.parentNode;

 

确实非常简单!可以肯定这个节点有子节点,因为已经有了一个:原来的图片。此外,完全不需要知道它是一个 divp 或者页面的 body,都没有关系!

插入新图片

现在得到了原来图片的父节点,可以插入新的图片了。很简单,有多种方法可以添加子节点:

  • insertBefore(newNode, oldNode)
  • appendChild(newNode)

因为希望把新图片放在旧图片的位置上,需要使用 insertBefore()(后面还要使用 removeChild() 方法)。可使用下面这行 JavaScript 代码把新图片元素插入到原有图片之前:

var imgParent = hatImage.parentNode;

imgParent.insertBefore(newImage, hatImage);

 

现在原图片的父元素有了两个 子元素:新图片和紧跟在后面的旧图片。必须指出,这里包围 这些图片的内容没有变,而且这些内容的顺序也和插入之前完全相同。仅仅是这个父节点中增加了一个子节点,即旧图片之前的新图片。

删除旧图片

现在只需要删除旧图片,因为网页中只需要新图片。很简单,因为已经得到了旧图片元素的父节点。只要调用 removeChild() 并把需要删除的节点传递给它即可:

var imgParent = hatImage.parentNode;

imgParent.insertBefore(newImage, hatImage);

imgParent.removeChild(hatImage);

 

现在,用新图片替换旧图片的工作已基本完成了。HTML 应该如清单 5 所示。


清单 5. 用新图片替换旧图片

 

<html>

 <head>

  <title>Magic Hat</title>

  <script language="JavaScript">

    function showRabbit() {

      var hatImage = document.getElementById("topHat");

      var newImage = document.createElement("img");

      newImage.setAttribute("src", "rabbit-hat.gif");

      var imgParent = hatImage.parentNode;

      imgParent.insertBefore(newImage, hatImage);

      imgParent.removeChild(hatImage);

    }

  </script>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" />

   </p>

  </form>                                                                     

 </body>

</html>

 

连接 JavaScript

最后一步,可能也是最简单的,就是把 HTML 表单连接到刚刚编写的 JavaScript 函数。需要每当用户点击 Hocus Pocus! 按钮的时候运行 showRabbit() 函数。为此只要向 HTML 中增加一个简单的 onClick 事件处理程序即可。

<input type="button" value="Hocus Pocus!" onClick="showRabbit();" />

 

这种简单的 JavaScript 编程应该非常容易了。将其添加到 HTML 页面中,保存它然后在 Web 浏览器中打开。页面初看起来应该和图 1 相同,但是点击 Hocus Pocus! 后应该看到图 3 所示的结果。


3. 兔子戏法
<!--[if !vml]-->兔子戏法<!--[endif]-->

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

替换图片,简单的办法

如果回顾替换图片的步骤,再看看节点的各种方法,可能会注意到方法 replaceNode()。该方法可用于把一个节点替换为另一个节点。再考虑一下前面的步骤:

  1. 创建新的 img 元素。
  2. 访问当前 img 元素的父元素,也就是它的容器。
  3. 在已有 img 元素之前 插入新的 img 元素作为该容器的子元素。
  4. 删除原来的 img 元素。
  5. 连接起来以便在用户点击 Hocus Pocus! 的时候调用刚刚创建的 JavaScript 函数。

使用 replaceNode() 可以减少需要的步骤数。可以将第 3 步和第 4 步合并在一起:

  1. 创建新的 img 元素。
  2. 访问当前 img 元素的父元素,也就是它的容器。
  3. 用创建的新元素替换旧的 img 元素。
  4. 连接起来以便在用户点击 Hocus Pocus! 的时候调用刚刚创建的 JavaScript 函数。

这看起来不是什么大事,但确实能够简化代码。清单 6 说明了这种修改:去掉了 insertBefore() removeChild() 方法调用。


清单 6. 用新图片替换旧图片(一步完成)

 

<html>

 <head>

  <title>Magic Hat</title>

  <script language="JavaScript">

    function showRabbit() {

      var hatImage = document.getElementById("topHat");

      var newImage = document.createElement("img");

      newImage.setAttribute("src", "rabbit-hat.gif");

      var imgParent = hatImage.parentNode;

      imgParent.replaceChild(newImage, hatImage);

    }

  </script>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" onClick="showRabbit();"  />

   </p>

  </form>                                                                     

 </body>

</html>

 

当然这不是什么大的修改,但是说明了 DOM 编码中一件很重要的事:执行一项任务通常有多种方法。如果仔细审阅可用 DOM 方法看看是否有更简单的方法可以完成任务,很多时候都会发现可以将四五个步骤压缩为两三个步骤。

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

<!--[if !vml]--><!--[endif]-->

回页首

 

替换图片,(真正)简单的办法

既然指出了执行一项任务几乎总是有更简单的方法,现在就说明用兔子图片替换帽子图片的简单得多 的办法。阅读本文的过程中有没有想到这种方法?提示一下:与属性有关。

要记住,图片元素很大程度上是由其 src 属性控制的,他引用了某个地方的文件(不论是本地 URI 还是外部 URL)。到目前为止,我们一直用新图片替换图片节点,但是直接修改已有图片的 src 属性要简单得多!这样就避免了创建新节点、寻找父节点和替换旧节点的所有工作,只要一步就能完成了:

hatImage.setAttribute("src", "rabbit-hat.gif");

 

这样就够了!看看清单 7,它显示了这种解决方案,包括整个网页。


清单 7. 修改 src 属性

 

<html>

 <head>

  <title>Magic Hat</title>

  <script language="JavaScript">

    function showRabbit() {

      var hatImage = document.getElementById("topHat");

      hatImage.setAttribute("src", "rabbit-hat.gif");

    }

  </script>

 </head>

 

 <body>

  <h1 align="center">Welcome to the DOM Magic Shop!</h1>

  <form name="magic-hat">

   <p align="center">

    <img src="topHat.gif" id="topHat" />

    <br /><br />

    <input type="button" value="Hocus Pocus!" onClick="showRabbit();"  />

   </p>

  </form>                                                                     

 </body>

</html>

 

这是 DOM 最棒的一点:更新属性的时候网页马上就会改变。只要图片指向新的文件,浏览器就加载该文件,页面就更新了。不需要重新加载,甚至不需要创建新的图片元素!结果仍然和图 3 相同,只不过代码简单得多了。

 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值