Pro Javascript Techniques第五章: 文档对象模型

 在过去十年里web开发所取得的所有进步当中,DOM(文档对象模型)脚本是开发者可用来改进其用户体验质量的最重要的技术。
  使用DOM脚本向页面加入非侵入的JavaScript(意味着它不会与不支持的浏览器或禁用了JavaScript的用户发生冲突),你将能提供各种你的用户可享受的现代浏览器的增强功能,同时又不会损害那些不能利用它们的用户。这么做的一个副作用是,你的所有代码最终都可以被很好的分离和更容易地管理。
  可喜的是,所有的现代浏览器都支持DOM并额外地支持一个当前HTML文档的内建的DOM表述。所有这些都很容易通过JavaScript访问,这为现代web开发者带来巨大的利益。理解怎样使用这一技术和怎样最好地发挥它的功效,能够给这你开发下一个web应用程序的提供一个良好的开端。
  本章中我将讨论与DOM相关的一些话题。考虑到你可能对DOM没有经验,我将从基础出发,涵盖所有的重要概念。对于已经熟悉了DOM的读者,我保证将会给出一些你肯定会喜欢并开始在自己的页面中使用的很酷的技术。

顶部
one by one
[广告] 【万网邮箱DIY,灵活购买】| 【西部数码】480元轻松自助建站
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#2
发表于 2007-4-13 21:02  资料  短消息  加为好友  QQ
文档对象模型简介
  
  DOM是由W3C制定的表示XML文档的标准方式。它未必是最快的、最轻便的、或者最易使用的,却是是最普及的,绝大多数web开发语言(如Java,Perl,PHP,Ruby,Python,及Javascript)都实现了对它的支持。DOM旨在为开发者提供一种直观的方式来导航于XML的层次结构中。即使你并不完全熟悉XML,你也会非常高兴地看到所有的HTML文档(在浏览器的眼中也就是XML文档)都有一个可供使用的DOM表述。

[ 本帖最后由 mozart0 于 2007-4-13 23:54 编辑 ]

顶部
one by one
[广告] 购买数字引擎双线路主机空间 | 优质域名主机首选时代互联
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#3
发表于 2007-4-13 21:02  资料  短消息  加为好友  QQ
导航DOM

  DOM中描述XML结构的方式是作为一种可导航的树。使用的所有术语与一个家族树(parents,children,sibling,等等)是近似的。与典型的家族树不同的是,XML文档以单个包含指向其子节点的指针的根节点(称为文档元素( document element))开始。每一个子结节又包括指回其父结节、兄弟结点和子节点的指针。
  DOM使用特定的术语来代表XML树中的各种对象。DOM树中的每一个对象都是一个 节点(node)[/i]。每个节点可以拥有不同的[i]类型(type),如元素(element),文本(text),或文档(document)。为了继续,我们需要等来了解DOM文档是什么样子的以及怎样在其中导航(一旦它已经构建完成)。通过一段简单的HTML片段,我们来考察这一DOM构建工作是怎样进行的。

<p><strong>Hello</strong> how are you doing?</p>

  这个片断的每一部分被分解成一个带有指向基直接亲属(父、子、兄弟)的指针的DOM节点。如果完全描绘出存在的关系,它将会是类似于图5-1。片段的每一部分(圆角盒子代表元素,方盒子代表文本节节点)与它所有的引用一起显示。
[attach]33187[/attach]
  图5-1. 节点间的关系

  每个DOM节点都包含一个指针的集合,它使用这些指针引用其亲属。你将使用这些指针来学习怎样导航DOM。图5-2显示了所有可用的指针。这些属性对每一个DOM节点都可用,是指向其它DOM元素的指针(或者是null,如果不存在对应元素的话)。
[attach]33188[/attach]
  图5-2. 使用指针导航DOM

  仅使用指针而导航到页面的任何元素元素和文本块是可能的。理解在现实环境里这一点怎样工作的最好的方式是来看一个普通的HTML页面,如程序5-1所示:

  程序5-1. 一个简单的HTML网页,兼一个简单的XML文档

<html>
<head>
    <title>Introduction to the DOM</title>
</head>
<body>
    <h1>Introduction to the DOM</h1>
    <p class="test">There are a number of reasons why the
        DOM is awesome, here are some:</p>
    <ul>
        <li id="everywhere">It can be found everywhere.</li>
        <li class="test">It's easy to use.</li>
        <li class="test">It can help you to find what you want, really quickly.</li>
    </ul>
</body>
</html>

  在这个示例文档中,根元素是<html>元素。在JavaScript访问这一个根元素是很轻松的:

  document.documentElement

  如同它的DOM节点一样,根结点拥有用来导航的所有指针。使用这些指针你就能开始浏览整个文档,导航到你想要的任何元素。例如,要得到<h1>元素,你可能使用以下语句:

  //Don't work!
  document.documentElement.firstChild.nextSibling.firstChild

  我们恰好撞上了我们的第一个暗礁:DOM指针既能指向文本节点也能指向元素。于是,上面的语句实际并不是指向<h1>元素;它反倒指向<title>元素。为什么会出这种事呢?这要归咎于XML的最棘手和最受争议的一个方面:空白(white space)。你可能会注意到的,在<html>和<head>元素之间,实际上有一个换行符,它被认为是空白,这意味着那里实际上首先有一个文本节点,而不是<head>元素。我们从中可以学到三件事:
  1. 当尝试只使用指针导航DOM的时候,编写漂亮、整洁的HTML标记可能反会使事情变得非常令人困惑。
  2. 仅仅使用DOM指针导航文档可能是非常的冗长和不实际。
  3. 通常,你并不需要直接访问文本节点,而是访问包绕它们的元素。
  这把我们导向一个问题:有没有一种更好的方式用来在文档找到元素呢?有!通过使用工具箱里的几个有用的函数,你可以轻易改善现有的方法,把DOM导航变得简单得多。

   处理DOM中的空白

  让我们先回到那个示例HTML文档。先前,你试图定位那个单独的<h1>元素却因无关的文本节点而遇上了困难。这对于单个的元素可能还是好的,但是倘若你想要找到<h1>后面的那元素呢?你仍然会遭遇那个臭名昭著的空白bug,不得不使用.nextSibling.nextSibling来跳过<h1>和<p>之间的换行符。 All is not lost though.(?)有一种技巧可以作为这一空白bug的补救办法,如程序5-2所示。这一特别的技巧去除了DOM文档所有的空白文本节点,便它变得更加易于穿行。这么做对你的HTML怎么渲染并没有明显的影响,却能使用你手工导航变得容易。应该注意的是,这个函数的结果并不是永久性的,每次HTML文档加载以后都需要重新运行。

  程序5-2. XML文档中空白bug的补救办法

function cleanWhitespace( element ) {
    //如果没有提供element,则处理整个HTML文档
    element = element || document;
    //使用firstChild作为开始指针
    var cur = element.firstChild;
    
    //遍历所有子节点
    while ( cur != null ) {
    
        //如果该节点是文本节点,且只含有空白
        if ( cur.nodeType == 3 && ! //S/.test(cur.nodeValue) ) {
            //删除些文本节点
            element.removeChild( cur );
        //否则,如果它是一个元素
        } else if ( cur.nodeType == 1 ) {
            //递归处理下一级节点
            cleanWhitespace( cur );
        }
        cur = cur.nextSibling; //移动到下一个子节点
    }
}

  比方说你想要在上面的示例文档这个函数以找到<h1>后面的那个元素。完成这一工作的代码会是类似这样的:

cleanWhitespace();
//获得文档元素
document.documentElement
    .firstChild //找到<head>元素
    .nextSibling //找到<body>元素
    .firstChild //找到<h1>元素
    .nextSibling //取得相邻的段落

  这是一种既有好处又有缺点的技巧。最大的好处是,当你试图导航DOM文档的时候你可以保持某种程度的逻辑清晰。但是,考虑到你必须遍历所有的DOM元素和文本节点来寻找只包含空白的节点,这一技巧非常之慢。如果你的文档包含大量的内容,它会明显地拖慢你站点的加载。而且,每次你往文档中注入新的HTML,你都需要重新扫描DOM的那一部分,确保没有附加的空白文本节点被加入。
  上述函数里一个重要的方面就是节点类型的使用。一个节点的类型可以通过检查其nodeType属性为特定值来判定。有许多种可能的值,但最经常碰到的是以下三种
   元素(nodeType=1): 匹配XML文档中的所有元素。例如,<li>,<a>,<p>,和<body>元素的nodeType全都是1。
   文本(nodeType=3): 匹配文档中所有的文本段。当在一个DOM结构中使用previousSibling的nextSibling导航时,你经常会在元素之间或元素内部遇到文本片段。
   文档(nodeType=9): 匹配一个文档的根元素。比如,在一个HTML文档里,它就是<html>元素。
  另外,(在非IE浏览器中)你可以使用常数来代表不同的节点类型。比如,代替记住1,3或9,你可以简单地使用document.ELEMENT_NODE,document.TEXT_NODE,或document.DOCUMENT_NODE。既然反复地清除DOM的空白文本节点大有累赘之嫌,我们自然应该寻求其它的方法来导航DOM。

   简单的DOM导航

  使用纯DOM导航的原理(拥有每个方向的导航指针)你可以开发出可能更适合你的导航HTML DOM文档的函数。这一特殊的原则的依据是:多数web开发者只针对DOM元素而很少对其间的文本节点导航。下面提供几个函数,可以用来代替标准的previousSibling,nextSIbling,firstChild,lastChild以及parentNode。程序5-3展示了一个返回元素的前一个元素的函数。类似于元素的previousSibling属性,如果没有前一个元素,该函数返回null。

  程序5-3. 用来查找元素的前一个兄弟元素的函数

function prev( elem ) {
    do {
        elem = elem.previousSibling;
    } while ( elem && elem.nodeType != 1 );
    return elem;
}

  程序5-4展示了一个返回元素的下一个兄弟元素的函数。与元素的nextSibling属性类似,当没有下一个元素时,函数返回null。

  程序5-4. 用来查找元素的后一个兄弟元素的函数

function next( elem ) {
    do {
        elem = elem.nextSibling;
    } while ( elem && elem.nodeType != 1 );
    return elem;
}

  程序5-5展示了一个返回元素的第一个子元素的函数,与元素的firstChild属性类似。

  程序5-5.

function first( elem ) {
    elem = elem.firstChild;
    return elem && elem.nodeType != 1 ?
        next ( elem ) : elem;
}

  程序5-5展示了一个返回元素的最后一个子元素的函数,与元素的lastChild属性类似。

  程序5-6.

Listing 5-6. A Function for Finding the Last Child Element of an Element
function last( elem ) {
    elem = elem.lastChild;
    return elem && elem.nodeType != 1 ?
        prev ( elem ) : elem;
}

  程序5-7展示了一个返回元素父元素的函数,与元素的parentNode属性类似。你可以提供一个可选的参数number,以一次向上移动几层——比如说,parent(elem,2)与parent(parent(elem))等价。

  程序5-7. 用来查找元素父元素的函数

function parent( elem, num ) {
    num = num || 1;
    for ( var i = 0; i < num; i++ )
        if ( elem != null ) elem = elem.parentNode;
    return elem;
}

  使用这些新的函数,你可以快速地浏览一个DOM文档,而无需担心元素之间的文本。例如,为了找到<h1>元素的下一个元素,像从前一样,你现在可以像下面这么做:

//查找<h1>的下一个元素
next( first( document.body ) )

  注意到这行代码的两个特点。第一,有一个新的引用:document.body。所有现代浏览器都在HTML DOM文档的body参数里提供一个对<body>元素的引用。你可以利用这一点使你的代码更加简短和更加可理解。第二,函数的书写方式是非常地违背直觉的。通常,当你想到导航时你可能会说:从<body>元素开始,得到第一个元素,再得到第二个元素。但是在它实际的书写方式里,好像是倒着来的。为了替代这一方式,我将会讨论一些使得你定制的导航代码更加清晰的办法。

   绑定到每一个HTML元素

  在Firefox和Opera里,有一种可用的非常强大的对象原型,称为HTMLElement,它允许你将函数和数据附加到每个单独的HTML DOM元素上。前面一节所介绍的函数是很呆板的,可以进行某种清理。一种完美的方式是把你的函数直接绑定到HTMLElement原型上,以此来把它们直接绑定到每一个单独的HTML元素。为了进行这一工作,对前面所建立的函数需要作三个更改:
  1. 在函数里最上面添加一行将使elem指向this,而不再是从参数列表取得。
  2. 删除那个你不再需要的元素参数。
  3. 将函数绑定到HTMLElement原型,这样你才能在第一个DOM中的HTML元素上使用它。
  举例来说,新的next函数将会是如程序5-8所示的样子。

  程序5-8. 向所有HTML DOM元素动态地绑定一个新的导航函数

HTMLElement.prototype.next = function() {
    var elem = this;
    do {
        elem = elem.nextSibling;
    } while ( elem && elem.nodeType != 1 );
    return elem;
};

  现在你可以像这样使用next函数(或者是经过造的前述的第一个函数):

//一个简单的例子:得到第一个的<p>元素
document.body.first().next()

  这使得你的代码更加清晰而易于理解,因为你能以自然思考的顺序书写代码。如果你对这种书写风格有兴趣,我极力推荐你去看看JQuery库,它极好地利用了这一技术。
   注意:因为HTMLElement只存在于三种现代浏览器中(Firefox,Safari,和Opera),你需要采取特殊的预防措施使它能够在IE中工作。Jason Karl(http://browserland.org)编写了一个特别便利的库,在两种不支持的浏览器中提供了对HTMLElement(及其它相关功能)的访问。关于此库的更多信息可以在这里找到:http://www.browserland.org/scripts/htmlelement/

   标准的DOM方法

  所有的现代DOM实现都包含几种使工作更加有条理的方法。将它们和一些自定义函数结合使用,DOM导航将会变成一种流畅得多的体验。首先,我们来看JavaScript DOM中包含的两个功能强大的函数:
   getElementById("everywhere"):此方法只能应用于document对象,它在所有元素中查找ID等于everywhere的元素。这一强大的函数是立即访问一个元素的最快的方式。
   getElementsByTagName("li"):此方法可以在任意元素上使用,它在所有后代元素中查找标签名为li的,并将它们作为一个(几乎与数组相同的)节点列表返回。
   警告:对HTML文档来说,getElementById会如你想象的那样工作:它检查所有的元素直到找到id属性与给定的值相同的那一个。然而,如果你载入一个远程的XML文档并使用getElementById(或使用JavaScript以外的另一种语言里的DOM实现),它默认并不根据id属性查找。它是由设计决定的;一个XML文档必须明确地(一般用XML定义或XML模式)指定id属性是什么。
   警告:getElementsByName返回一个节点列表。该结构外观的行为都跟通常的JavaScript数组十分相似,但是有一个重要的例外:它不具有通常的.push(),.pop(),.shift()等等这些JavaScript数组所具有的方法。使用getElementsByName时牢记这一点,会省去你许多的疑惑。
  这两个方法在所有的现代浏览器中都是可用的,且对于定位特定元素极有帮助。回到前面我们试图找到<h1>元素的例子,现在我们可以像下面这么做:

document.getElementsByTagName("h1")[0]

  这段代码会有保障地工作并总是返回文档中的第一个<h1>元素。回到前面的示例文档,假设你想到得到所有的<li>元素并给它们加上边框:

var li = document.getElementsByTagName("li");
for ( var j = 0; j < li.length; j++ ) {
    li[j].style.border = "1px solid #000";
}

  再一次地,我们回头看查找第一个<h1>元素后面的元素的问题,完成这一工作的代码到期可以减短得更多:

//找到第一个<h1>元素的后一个元素
next(tag("h1")[0]);

  这些函数提供了快速得到你想操作的DOM元素的能力。在学习使用这一能力来修改DOM之前,你需要先快速地看看你的脚本第一次执行以后DOM加载的问题。

[ 本帖最后由 mozart0 于 2007-4-15 09:51 编辑 ]



 附件: 您所在的用户组无法下载或查看附件,您需要注册/登陆后才能查看!
顶部
one by one
[广告] 网站博客卖广告推荐:阿里妈妈
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#4
发表于 2007-4-13 21:03  资料  短消息  加为好友  QQ
等待HTML DOM加载

  操作HTML DOM文档的一个难题是,你的JavaScript代码可能在DOM完全载入之前运行,这会导致你的代码产生一些问题。页面加载时浏览器内部操作的顺序大致是这样的:
  1. HTML被解析。
  2. 外部脚本/样式表被加载。
  3. 文档解析过程中内联的脚本被执行。
  4. HTML DOM构造完成。
  5. 图像和外部内容被加载。
  6. 页面加载完成。
  头部包含的和从外部文件中载入的脚本实际上在HTML DOM构造好之前就执行了。正如前面提到的,这一个问题是很重要的,因为在那两种地方的执行的所有脚本将不能访问DOM。可喜的是,存在许多绕开这一问题的办法。
  
   等待页面加载

  到目前为止,最常用的技术是在任何DOM操作之前简单地等待整个页面加载。使用这一技术,可以通过简单地给window对象的load事件附加一个在页面载入后触发的函数。在第六章中我将讨论关于事件的更多细节。程序5-10展示了一个在页面加载完成后执行DOM相关代码的例子。

  程序5-10. 为window.onload属性附加回调函数的addEvent函数

//等待页面加载完成
//(使用了下一章描述的addEvent函数)
addEvent(window, "load", function() {
    //执行HTML DOM操作
    next( id("everywhere") ).style.background = 'blue';
});

  尽管这一操作可能是最简单的,它也将总是最慢的。从加载操作的顺序中,你可能已发现页面加载完成绝对是最后一步。这意味着如果在你的页面上有大量的图像、视频等等,你的用户在JavaScript最终执行前得等待很大一阵子。

   等待大部分DOM加载

  第二种技术很迂回,不太推荐使用。如果你还记得,我在上一节里说了,内联的脚本是在DOM构造以后执行的。这是一个半真半假的说法。那些脚本实际上是在DOM构造时遇上了就执行的。这就是说如果你有一段内联的脚本嵌在页面的中间部分,则该脚本只能立即拥有前半部分DOM的访问权。然而,把脚本作为非常靠后的元素嵌入页面中,就意味着你能够有效地对先于它出现的所有的DOM元素进行访问,获得一种假冒的模拟DOM加载的方式。这种方法的典型实现通常如程序5-11所示。

  程序5-11. 通过向HTML DOM的结尾置入(包含函数调用的)<script>标签来判定DOM是否已经加载

<html>
<head>
    <title>Testing DOM Loading</title>
    <script type="text/javascript">
        function init() {
            alert( "The DOM is loaded!" );
            tag("h1")[0].style.border = "4px solid black";
        }
    </script>
</head>
<body>
    <h1>Testing DOM Loading</h1>
    <!--这里是大量的HTML -->
    <script type="text/javascript">init();</script>
</body>
</html>

  在这个例子里,一个内联脚本作为DOM的最后一个元素;它将是最后一个被解析和执行的。它所做的唯一的事情是调用init函数(函数内部应包含你想要处理的任何DOM相关的代码)。这一解决方案的存在的最大的问题在于,它是混乱的:给你的HTML里加入了额外的标记,只为了判定DOM是否已经加载。

   断定DOM何时加载完成

  最后一种可用来监视DOM加载的技术,可能是最复杂(从实现的角度来看)但也是最有效的。它结合了绑定到window的load事件的简易性和内联脚本技术的速度。
  这一技术的原理是在不阻塞浏览器的前提下尽可能快地反复检查HTML DOM是否已经具有了你所需的特性。有几种东西可以被检查以判断HTML文档是否已经可以操作了:
  1. document: 你需要检查DOM document是否已经存在。如果你检查得够快的话,它一开始可能仅仅是undefined。
  2. document.getElementsByTagName和document.getElementByID: 检查document是否已经具备了经常使用的getElementsByTagName和getElementById函数;这些函数将在它们准备好被使用以后存在。
  3. document.body: 作为额外的保障,检查<body>元素是否已完成被载入。理论上讲,前面的检查应该已经足够了,但是我发现过它们还不够好的例子。
  使用这些检查,你将对DOM何时准备好被使用有一个足够好的把握(好到可能只错过了几毫秒)。这一方法近乎没有瑕疵。仅使用前面的检查,脚本可以在所有的现代浏览器里运行得相对很好了。然而,Firefox某些新的缓存机制的实现,导致了window的load事件实际上能够在你的脚本判断DOM是否就绪之前就触发。为了利用这一优势,我也加入了对window的load事件的检查,希望获得一些额外的速度。
  最终,domReady函数在DOM就绪之前一直在收集所有的待运行函数的引用。一旦DOM确实准备好了,就遍历这些引用并一个一个地执行它们。程序5-12展示了一个可用来监视DOM何时完全载入的函数。

程序5-12. 监视DOM直到它准备好的一个函数

function domReady( f ) {
    //如果DOM已经载入,立即执行函数
    if ( domReady.done ) return f();
    //如果我们已经添加过函数
    if ( domReady.timer ) {
        //则将函数添加到待执行的函数列表
        domReady.ready.push( f );
    } else {
        //为页面完成加载时附加一个事件,以防它率先发生
        //使用了addEvent函数
        addEvent( window, "load", isDOMReady );
        //初始化待执行函数的数组
        domReady.ready = [ f ];
        //尽可能快地检查DOM是否已就绪
        domReady.timer = setInterval( isDOMReady, 13 );
    }
}
//检查DOM是否已经准备好导航
function isDOMReady() {
    //如果我们断定页面已经加载完成了,则返回
    if ( domReady.done ) return false;
    //检查一些函数和元素是否已可访问
    if ( document && document.getElementsByTagName &&
        document.getElementById && document.body ) {
        //如果它们已就绪,则停止检查
        clearInterval( domReady.timer );
        domReady.timer = null;
        
        //执行所有正在等待的函数
        for ( var i = 0; i < domReady.ready.length; i++ )
            domReady.ready[i]();
        //记住现在我们已经完成
        domReady.ready = null;
        domReady.done = true;
    }
}

现在我们应该看看这在一个HTML文档里会是什么样。使用domReady函数就像使用addEvent函数(见第6章)一样,绑定你的特定函数到文档准备好导航和操作的时候被触发。在下面的例子里我把domReady函数放入了一个名为domready.js的外部JavaScript文件里。程序5-3展示了怎样使用新的domReady函数来监视DOM何时已载入。

  程序5-13. 使用domReady函数在判定DOM何时准备好导航和修改

<html>
<head>
    <title>Testing DOM Loading</title>
    <script type="text/javascript" src="domready.js"></script>
    <script type="text/javascript">
        function tag(name, elem) {
            //如果上下文元素未提供,则搜索整个文档
            return (elem || document).getElementsByTagName(name);
        }
        domReady(function() {
            alert( "The DOM is loaded!" );
            tag("h1")[0].style.border = "4px solid black";
        });
    </script>
</head>
<body>
    <h1>Testing DOM Loading</h1>
    <!--这里是大量的HTML -->
    </body>
</html>

  既然你了解了用来导航一般的XML DOM文档的和克服HTML DOM文档加载难题的几种方法,这个问题应该被摆在眼前了:有没有更好的在HTML文档中查找元素的方法呢?可喜的是,答案是响亮的"有"。

[ 本帖最后由 mozart0 于 2007-4-13 23:53 编辑 ]

顶部
one by one
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#5
发表于 2007-4-13 21:03  资料  短消息  加为好友  QQ
在HTML文档中查找元素

  在一个HTML文档中查找元素的方式常常与在一个XML文档中有很大的不同。考虑到现代HTML实际上是XML的一个子集,这看起来可能有些矛盾;但是HTML文档包含一些你可以利用的基础的不同点。
  对JavaScript/HTML开发者来说,最重要的两个优势是CSS类的使用的CSS选择符的知识。记住这些,你就可以创建一些强大的函数用来使得DOM导航更加简单和可理解。

   通过类名查找元素

  用类名定位元素是一种广泛流传的技术,由Simon Willison( http://simon.incutio.com)于2003年推广,最初由Andrew Hayward( http://www.mooncalf.me.uk)编写。这一技术是非常易行的:遍历所有元素(或所有元素的一个子集),选出其中具有特定类名的。程序5-14展示了一种可能的实现。

  程序5-14. 从所有元素中找出具有特定类名的元素的一个函数

function hasClass(name,type) {
    var r = [];
    //限定类名(允许多个类名)
    var re = new RegExp("(^|//s)" + name + "(//s|$)");
    //用类型限制搜索范围,或搜索所有的元素
    var e = document.getElementsByTagName(type || "*");
    for ( var j = 0; j < e.length; j++ )
        //如果元素类名匹配,则加入到返回值列表中
        if ( re.test(e[j]) ) r.push( e[j] );
    
    //返回匹配的元素
    return r;
}

  现在你可以通过一个指定的类名使用些函数来快速地查找任何元素,或特定类别的任何元素(比如,<li>或<p>)。指定要查找的标签名总会比查找全部(*)要快,因为查找元素的范围被缩小了。比如,在我们的HTML文档里,如果想要查找所有类名包含"test"的元素,你可以这么做:

hasClass("test")

  如果你只想查找类名包含"test"的所有<li>元素,则这样:

hasClass("test","li")

  最后,如果你想找到第一个类名包含"test"的<li>元素,则这么做:

hasClass("test","li")[0]

  这个函数单独使用已经很强大了,而当与getElementById和getElementByTagname联合使用时,你就拥有了非常强大的可完成最复杂的DOM工作的一套工具。
   
  通过CSS选择符查找元素

  作为一个web开发者,你已经知道一种选择HTML元素的方式:CSS选择符。CSS选择符是用来将CSS样式应用于一组元素的表达式。随着CSS标准的每一次修订(1,2和3),更多的功能被加入了选择符规范中,允许开发者更容易地精确确定他们想要的元素。不幸的是,浏览器一直是极其缓慢地提供CSS2和CSS3选择符的完全实现,这意味着你可能还不知道它们所提供的一些很酷的新功能。如果你对CSS的所有的新而酷的功能感兴趣,我建议探究一下W3C的关于该项目的网页:
   CSS1 selectors: http://www.w3.org/TR/REC-CSS1#basic-concepts/
   CSS2 selectors: http://www.w3.org/TR/REC-CSS2/selector.html
   CSS3 selectors: http://www.w3.org/TR/2005/WD-css3-selectors-20051215/
  每种CSS选择符规范中可用的的功能大体上是相似的,因为后继的版本总是包含前面版本的所有功能。然而,每一个版本都加入了一些新的功能。举例来说,CSS2包含属性和子代选择符,而CSS3提供了额外的语言支持,通过属性类型和否定来选择。比如,下面都是有效的CSS选择符:
   #main div p(译注:原文写作"#main <div> p",疑有误): 此表达式查找一个ID为"main"的元素的所有的后代<div>元素的所有的后代<p>元素。这是一个正确的CSS1选择符。
   div.items > p: 此表达式查找所有的类名包含"items"的<div>元素,然后找出所有的子代<p>元素。这是一个有效的CSS2选择符。
   div:not(.items): 此表达式查找所有类名不包含"items"的<div>元素。这是一个有效的CSS3选择符。
  现在,你可能会奇怪为什么我会讨论CSS选择符,如果不能实际地使用它们来定位元素(只应用于CSS样式)的话。一些富有进取精神的开发者在着手于此,创建了能够处理从CSS1到全部的CSS3的CSS选择符实现。使用这些库,你将能够快速而容易地选择任何元素并对它们进行操作。

  cssQuery

  第一个公开可用的支持全部CSS1-CSS3的库被称为cssQuery,由Dean Edwards( http://dean.edwards.name)创建。它背后的前提是简单的:给出一个选择符,cssQuery将找到所有匹配的元素。另外,cssQuery被分割成许多子库,每一个对应于CSS选择符的一个时期,这意味着如果不需要CSS3支持的话,你可以把它排除在外。这一特别的库彻底而广泛,可工作于所有的现代浏览器中(Dean是一个坚定的跨浏览器支持者)。为了使用这个库,你需要提供选择器,以及可选的在其中搜索的上下文元素。下面是几个示例:

//查找<div>元素的所有子代<p>元素
cssQuery("div > p");
//查找所有的<div>,<p>,<form>
cssQuery("div,p,form");
//查找所有的<p>和<div>,然后查找他们内部的所有<a>元素
var p = cssQuery("p,div");
cssQuery("a",p);

  执行cssQuery函数会返回一个匹配元素的数组。你可以对它实施操作,就好像你刚刚执行了一次getElementsByTagName。比如说,为了给所有链接到Google的链接加上边框,你可以执行以下操作:

//为所有指向Google的链接加上边框
var g = cssQuery("a[href^='google.com']");
for ( var i = 0; i < g.length; i++ ) {
    g[i].style.border = "1px dashed red";
}

  关于cssQuery的更多信息及完整的源代码下载可以在Dean Edwards的网站上找到: http://dean.edwards.name/my/cssQuery/
   提示:Dean Edwards是一位JavaScript奇才;他的代码绝对是令人吃惊的。我极力推荐你到他的cssQuery库里去逛逛,至少看看优秀的可扩展的JavaScript代码是怎么写的。

  jQuery

  这是JavaScript库的世界里新近的加入者,但是提供了一些值得注意的编写JavaScript代码的方式。我最初只是想把他写成“简单的”CSS选择符库(跟cssQuery相似),直到Dean Edwards发布他的杰出的cssQuery库迫使这些代码向另一个不同的方向发展。这个库提供完全的CSS1-CSS3选择符的支持以及一些基本的XPath功能。在此之上,它还提供了进行更深入的DOM导航和操作的能力。跟cssQuery一样,jQuery也完全支持现代浏览器。这里有几个使用jQuery自定义的CSS的XPath的混合物选择元素的例子:

//查找所有的类名包括"links"且其内部有<p>元素的<div>元素
$("div.links[p]")
//查找所有<p>元素和<div>元素的后代
$("p,div").find("*")
//查找不指向Google的所有<a>超链接
$("a[@href^='google.com']:even")

  为了使用jQuery得到的结果,你有两种选择。首先,你可以执行$("expression").get()来得到匹配元素的一个数组——与cssQuery完全相同的结果。你可以做的第二件事是使用jQuery的独有的内建函数操作CSS和DOM。于是,回到用cssQuery为所指向Google的链接加边框的例子,你可以这么做:

//为所有指向Google的链接加上边框
$("a[@href^=google.com]").css("border","1px dashed red");

  在jQuery的项上网站上可以找到大量的示例、演示和文档,以及可定制的下载: http://jquery.com
   注意:应该指出的是,无论是cssQuery还是jQuery,实际上都不要一定求使用HTML文档来导航;它们适用于任何XML文档。下节的XPath将为你讲述纯XML形式的导航。

  XPath

  XPath表达式是一种导航XML文档的极其强大的方法。XPath已经存在了好几年;几乎可以说只要有DOM的实现,就会有XPath紧随其后。XPath表达式要比用CSS选择符可以写出的任何东西都强大得多,即便他们更加冗长。表5-1列举了一些不同的CSS选择符与XPath表达式的并行比较。
  表5-1. CSS3选择符与XPath表达式的比较
  ————————————————————————————————————
  目标                                          CSS3                     XPath
  ————————————————————————————————————
  所有元素                                          *                     //*
  所有<p>元素                                   p                     //p
  所有子元素                                   p>*                     //p/*
  特定ID的元素                                   #foo                     //*[@id='foo']
  特定class的元素                                   .foo                     //*[contains(@class,'foo')]
  带属性的元素                                   *[title]                     //*[@title]
  <p>的第一个子元素                            p>&.first-child       //p/*[0]
  拥有一个子元素的所有<p>元素              不能实现              //p[a]
  下一个元素                                          p+*              //p/following-sibling::*[0]
  ————————————————————————————————————
  如果前面的表达式激起了你的兴趣,我推荐你去浏览两个XPath规范(不过,XPath 1.0 通常是唯一被现代浏览器所完全支持的),感觉一下那些表达式是如何工作的。
   XPath 1.0: http://www.w3.org/TR/xpath/
   XPath 1.0: http://www.w3.org/TR/xpath20/
  如果你想对该主题进行深入的研究,我推荐你阅读Elliotte Harold和Scott Means所著的XML in a Nutshell(O'Reilly,2004),或者Jeni Tennison所著的XSLT 2.0:From Novice To Professional(Apress,2005)。另外,有一些很好的教程可以帮助你开始使用XPath:
   W3Schools的XPath教程: http://w3schools.com/xpath/
   ZVON XPath教程: http://zvon.org/xxl/XPathTutorial/General/examples.html
  目前,浏览器对XPath的支持是零星的;IE和Mozilla都支持全部(尽管各不相同)的XPath实现,而Safari和Opera都只有正在开发中的版本。为解决这一问题,有几种完全用JavaScript编写的XPath实现。它们一般都很慢(与基于浏览器的XPath实现相比),但是可以在所有浏览器里稳定地工作。
   XML for Script: http://xmljs.sf.net/
   Google AJAXSLT: http://goog-ajaxslt.sf.net
  另外,一个名为Sarissa( http://sarissa.sf.net)的项目立志于针对每种浏览器实现创建一个通用的包装。这能给你只须一次编写XML访问代码的能力,而仍能获得浏览器所支持的XML解析的所有速度优势。这一技术最大的问题是在Opera和Safari浏览器里它仍缺乏对XPath的支持。
  与广泛支持的纯JavaScript方案相比,使用浏览器内建的XPath通常被认为是实验性的技术。但是,XPath的使用和流行只会增长,它肯定应该被看作CSS选择器王位的强劲的竞争者。
  既然你已经拥有了定位任何一个甚至是一组DOM元素必须的知识和工具,我们现在应该讨论你可以使用该能力做些什么。从属性的操作到DOM元素的添加或删除,一切都是可能的。

[ 本帖最后由 mozart0 于 2007-4-13 23:52 编辑 ]

顶部
one by one
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#6
发表于 2007-4-13 21:03  资料  短消息  加为好友  QQ
获取元素的内容

  所有的DOM元素可以包含一种或三种东西:文本,元素,或文本与元素的混合。大致说来,最常见的是第一种和第三种情况。在这一节里你将学到检索元素内容的几种常见的方式。

   获取元素内的文本

  对于新接触DOM的人来说,获取元素内部的文本可能是最令人困惑的任务。然而,它也是一种在HTML DOM和XML DOM里都能需要的,因而了解怎样实现将很适合你。在图5-3中的示例DOM结构里,一个根元素<p>包含了一个<strong>元素和一个文本块。<strong>元素本身又包含了一个文本块。
[attach]33279[/attach]
  图5-3. 同时包含元素和文本的示例DOM结构

  我们来看看怎样取得这些元素中每一个元素的文本。<strong>元素是最容易拿来作为开始的,它仅包含一个文本节点。
  应该注意的是在所有的不基于Mozilla的浏览器里,存在一个属性inenrText用来获取元素内部文本。它在这方面极其便利。不幸的是,因为支持它的浏器在浏览器市场里并不占有显著份额(译注:估计作者没怎么用过IE),而且它不能在XML DOM文档里运行,你仍然需要探索一种可行的替代方案。
  获取元素的文本内容的诀窍在于,你需要记住文本并不是直接包含在元素里的,它被包含在子文本节点里(这可能看起来有点奇怪)。程序5-15展示了怎样用DOM取出一个元素内部的文本。设其中的变量strongElem包含了对<strong>元素的引用。

  程序5-15. 获取<strong>元素的文本内容

//非Mozilla浏览器:
strongElem.innerText
//所有平台:
strongElem.firstChild.nodeValue

  知道了怎样得到单个元素的文本内容,我们再来看看怎样取得<p>元素内文本内容的联合。在此过程中,也可以开发出一个可以获取任何元素的文本内容的通用函数,不论该元素实际上包含什么,如程序5-16所示。调用text(Element)将会返回组合了该元素及其所有后代元素包含的文本的一个字符串。

  程序5-16. 检索元素文本的一个通用函数

function text(e) {
    var t = "";
    //如果传递的是元素,则取其子元素,
    //否则假定它是一个数组
    e = e.childNodes || e;
    //检查所有子节点
    for ( var j = 0; j < e.length; j++ ) {
        //如果它不是一个元素,追加其文本到返回值
        //否则,对其所有子元素递归处理
        t += e[j].nodeType != 1 ?
            e[j].nodeValue : text(e[j].childNodes);
    }
    
    //返回所得的文本
    return t;
}

  有了一个可用来获取任何元素文本内容的函数,你现在可以用如下代码检索前面例子中的<p>元素的文本内容:

//获取<p>元素的所有文本内容
text( pElem );

  这个函数特别美妙的一点是不论在XML还是HTML的DOM中它都可以有保障地运行,也就是说你现在拥有了检索任何元素文本内容的一致的方法。

   获取元素内的HTML

  与获取元素内部文本相反,获取元素内部的HTML是可以执行的最容易的DOM任务之一。因为IE开发组所开发的一个功能,所有的现代浏览器里的任何HTML DOM元素都包含一个额外的属性:innerHTML。通过这一属性你就可以获得一个元素内部的所有HTML和文本。另外,使用innerHTML也非常快,速度通常是递归获取元素文本内容的很多倍。然而,事情也不是尽善尽美。怎样实现innerHTML属性依赖于浏览器,因为在方面并不存在真正的标准,浏览器可以返回它认为有价值的任何东西。比如说,这里有一些你在使用innerHTML属性时会遇上的一些怪异的bug:
  1. 基于Mozilla的浏览器在innerHTML语句里不会返回<style>元素。
  2. IE返回的所有元素标签都是大写的,这在寻求一致性时可能会十分令人沮丧。
  3. innerHTML属性只对HTML DOM文档元素里可用;尝试将它用于XML DOM文档将会得到null值。
  innerHTML的使用是很简单的;访问该属性就可以得到一个包含元素HTML内容的字符串。如果元素不包含子元素而只含有文本,返回HTML内容将只包括文本。为了弄清它是怎样工作的,我们来看看图5-2中所示的两个元素:

//获取<strong>元素的innerHTML
//将返回"Hello"
strongElem.innerHTML
//获取<p>元素的innerHTML
//将返回"<strong>Hello</strong> how are you doing?"
pElem.innerHTML

  如果你确定你的元素里只包含文本,这个方法可以作为复杂的元素文本内容获取的超级简单的替换方式。另一方面,能够检索元素的HTML内容意味着你现在可以建立一些很酷的使用了即时编辑功能的动态应用程序——关于此话题的更多内容可在第十章找到。

[ 本帖最后由 mozart0 于 2007-4-14 23:50 编辑 ]



 附件: 您所在的用户组无法下载或查看附件,您需要注册/登陆后才能查看!
顶部
one by one
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#7
发表于 2007-4-13 21:03  资料  短消息  加为好友  QQ
操作元素属性

  紧跟着检索元素内容,获取和设置元素的属性值也是最常进行的操作。典型地,元素具有的属性列表是与收集自元素本身XML表示的信息一起预载入的,并存储在一个关联数组中供稍后访问。比如如下网页中的HTML片段:

<form name="myForm" action="/test.cgi" method="POST">
    ...
</form>

  一旦它被载入DOM,HTML表单元素(变量formElem)将拥有一个关联数组,你可以从中收集"名称/值"对。这一结果类似于以下形式:

formElem.attributes = {
    name: "myForm",
    action: "/test.cgi",
    method: "POST"
};

  使用属性数组判定一个元素是具有某种属性绝对应该是很普通的,但是有一个问题:出于某种原因,Safari不支持这一点。更过分的是,有很大潜在价值的hasAttribute函数在IE里不被支持。那么应该怎么确定一个属性是不是存在呢?一个可能的方式是使用getAttribute函数(此函数将在下节讨论)并测试返回值是否为null,如程序5-17所示。

  程序5-17. 判定元素是否具有指定属性

function hasAttribute( elem, name ) {
    return elem.getAttribute(name) != null;
}

  有了这个函数,以及知道属性是怎么使用的,现在你已经准备好检索和设置属性值了。

   获取和设置属性值

  存在两种不同的方式从一个元素获取属性值,依赖于你使用的DOM文档类型,。如果你希望安全并总是使用兼容XML DOM的方法,可以使用getAttribute和setAttribute方法。它们可以以这种方式被使用:

//得到一个属性
id("everywhere").getAttribute("id")
//设置一个属性值
tag("input")[0].setAttribute("value","Your Name");

  除了这一对标准的getAttribute/setAttribute方法以外,HTML DOM还有一个额外的属性集,行为类似于属性的获取器/设置器。这在现代的DOM实现(不过只保证HTML DOM文档)中是普遍可用的,因此编写更短的代码时使用它们可以给你很大的优势。下面的代码说明怎样使用这种方法获取和设置DOM元素属性:

//快速获取一个属性
tag("input")[0].value
//快速设置一个属性
tag("div")[0].id = "main";

  在属性中有一些例外的情况是你应该意识到的。最常碰到的是访问类名属性的问题。在所有的浏览器中,为了一致地操作类名,你必须使用elem.className属性访问className属性,以代替本应更合适的getAttribute("class")。这一问题同样也出现在属性"for"上,它被重命名为"htmlFor"。另外,这种情况还见于两个CSS属性:cssFloat和cssText。这种特殊的命名方式的出现是因为class,for,float,和text这些单词是JavaScript中的保留字。
  为了解决这些特例带来的问题并简化整个读取和设置正确的属性的过程,你应该使用一个为你照顾这些特殊情况的函数。程序5-18展示了一个设置和获取元素属性的函数。使用两个参数调用该函数,如attr(element,id),将返回属性的值。使用三个参数调用该函数,如attr(element,class,test),将设置该属性的值并返回新的值。

  程序5-18. 获取和设置元素的属性值

function attr(elem, name, value) {
    //确保传递进来的是一个有效的属性名称
    if ( !name || name.constructor != String ) return '';
    
    //判定属性名称是不是异常的命名情况之一
    name = { 'for': 'htmlFor', 'class': 'className' }[name] || name;
    //如果用户正在设置值
    if ( typeof value != 'undefined' ) {
        //首先用快速的方法设置
        elem[name] = value;
        
        //如果可以,使用setAttribute
        if ( elem.setAttribute )
            elem.setAttribute(name,value);
    }
    //返回属性的值
    return elem[name] || elem.getAttribute(name) || '';
}

  拥有一个访问和改变属性的标准方式而无须顾及它们的实现,这是一个很强大的工具。程序5-19的例子展示了在一些通常的情况下怎样使用attr函数来简化属性的处理过程。

  程序5-19. 使用attr函数来设置和获取DOM元素的属性值

//为第一个<h1>元素设置类
attr( tag("h1")[0], "class", "header" );
//为每一个<inpu>元素设置值
var input = tag("input");
for ( var i = 0; i < input.length; i++ ) {
    attr( input[i], "value", "" );
}
//为一个名为"invalid"的<input>元素添加边框
var input = tag("input");
for ( var i = 0; i < input.length; i++ ) {
    if ( attr( input[i], "name" ) == 'invalid' ) {
        input[i].style.border = "2px solid red";
    }
}

  到目前为止,我只讨论了在DOM里固有的属性(如ID,class,name,等等)值的获取/设置。但是,有一个非常便利的技术是,设置/获取非传统的属性。比如说,你可以设置一个新的属性(只能通过访问元素的DOM版本看到)然后在稍后检索它,而无须修改文档的物理属性。举例来说,假设你有一个名词及其定义的列表,想要实现当一个名词被点击时,将它的定义展开。这一结构的HTML将大致如程序5-20所示。

  程序5-20. 带有隐藏了定义的定义列表的HTML文档

<html>
<head>
    <title>Expandable Definition List</title>
    <style>dd { display: none; }</style>
</head>
<body>
    <h1>Expandable Definition List</h1>
    <dl>
        <dt>Cats</dt>
        <dd>A furry, friendly, creature.</dd>
        <dt>Dog</dt>
        <dd>Like to play and run around.</dd>
        <dt>Mice</dt>
        <dd>Cats like to eat them.</dd>
    </dl>
</body>
</html>

  我将在第六章谈到与事件有关的更多细节,而现在我将试图保持我们的代码足够的简单。这便有了一个快速的脚本,允许你通过点击被定义的名词而显示(或隐藏)定义本身。这个脚本应该包含在页面的头部或从一个外部文件被包含。程序5-21展示了建立一个可展开的定义列表所需的代码。

  程序5-21. [color]Allowing for Dynamic Toggling to the Definitions(?)

// Wait until the DOM is Ready
domReady(function(){
    //找到所有的定义名词
    var dt = tag("dt");
    for ( var i = 0; i < dt.length; i++ ) {
        //监视用户对该名词的点击
        addEvent( dt[i], "click", function() {
            //检查定义是否已经展开
            var open = attr( this, "open" );
            //切换定义的显示状态
            next( this ).style.display = open ? 'none' : 'block';
            //记住定义是否已经展开
            attr( this, "open", open ? '' : 'yes' );
        });
    }
});

  了解怎样穿行于DOM中和怎样检查并修改属性之后,我们再来学习怎样创建新的DOM元素,将它们插入你希望的地方,并在你不再需要它们的时候将其删除。

[ 本帖最后由 mozart0 于 2007-4-15 00:02 编辑 ]

顶部
one by one
mozart0 [楼主]

匪徒田老大
版主


帖子 2326
体力 6628
威望 177
注册 2003-6-18

#8
发表于 2007-4-13 21:04  资料  短消息  加为好友  QQ
修改DOM

  通过了解如何修改DOM,你将能够在DOM中为所欲为:从实时创建自定义的XML文档到建立适合用户输入的动态表单;可能性是无限的。修改DOM分成三个步骤:首先创建一个新的元素,然后将它插入到DOM,最后把它再删除(如果需要的话)。

   使用DOM创建节点

  修改DOM背后的主要方法是createElement函数,它给你实时创建新元素的能力。然而,新的元素在你创建它的时候并没有立即插入到DOM中(这是DOM初学者普遍迷惑的一点)。首先,我来集中说说DOM元素的创建。
  createElement方法授受一个参数,元素的标签名,并返回元素的虚拟的DOM表示——不包含任何的属性和样式。如果你正在用XSLT生成的XHTML页(或者是指明了精确的内容类型的XHTML页)开发应用程序,你必须记住你实际上正在使用XML文档,并且你所有的元素都需要拥有正确的XML名称空间与之相关联。为了无缝地解决这一问题,你可以使用一个简单的函数来无声地测试你正使用的HTML DOM文档是否具有创建带名称空间的新元素的能力(XHTML DOM文档的特性)。如果是在这一情况下,你必须用正确的XHTML名称空间来创建新的元素。如程序5-22所示。

  程序5-22. 新建DOM元素的一个通用的函数

function create( elem ) {
    return document.createElementNS ?
        document.createElementNS( 'http://www.w3.org/1999/xhtml', elem ) :
        document.createElement( elem );
}

  例如,使用这个函数你可以创建简单的<div>元素并给它附加一些额外的信息。

var div = create("div");
div.className = "items";
div.id = "all";

  另外,应该注意的是有一个用来创建新的文本节点的叫做createTextNode的方法。它接受单个参数(你相要放在该节点里的文本),返回所创建的文本节点。
  使用新建的DOM元素的文本节点,你现在可以在你需要地方将它们正确地插入DOM文档。

   插入到DOM

  即使对于有经验的人来说,插入到DOM也是非常令人困惑的而且有时会感觉很不灵活。在你的工具库里有两个函数可用来完成这一工作。
  第一个函数,insertBefore,允许你将元素插入到另一个子元素的前面。当你使用这一函数的时候,代码会像是这个样子的:

parentOfBeforeNode.insertBefore( nodeToInsert, beforeNode );

  我使用的帮助记住其参数顺序的方法是短语"将第一个元素插入(insert)到第二个之前(before)"。我将会给你一个在一分钟之内记住这个的更容易的方式。
  既然你拥有了一个在其它节点前面插入节点的函数,你应该问你自己了:“那我怎样把节点作为最后一个子节点插入呢?”有一个叫做appendChild的另一个函数正好让你那么做。在一个元素上调用appendChild,将把指定的节点追加到元素的子节点列表的末尾。使用些函数的大致代码如下:

parentElem.appendChild( nodeToInsert );

  为了避免记忆insertBefore和appendChild的特殊的参数顺序,你可以使用我创建的用来解决这一问题的两个辅助函数:使用5-23和5-24中的新的函数,参数的顺序总是先是与你要插入的元素/节点相关的元素然后是你要插入的元素/节点。另外,before函数允许你有选择性地提供父元素,可能为你省去一些代码。最后,这两个函数都允许你传入一个将被插入/追加的字符串并为你将它自动地转换成一个文本节点。推荐传递一个父元素作为引用(以防elem参数碰巧是null)。

   插入到DOM

  即使对于有经验的人来说,插入到DOM也是非常令人困惑的而且有时会感觉很不灵活。在你的工具库里有两个函数可用来完成这一工作。
  第一个函数,insertBefore,允许你将元素插入到另一个子元素的前面。当你使用这一函数的时候,代码会像是这个样子的:

parentOfBeforeNode.insertBefore( nodeToInsert, beforeNode );

  我使用的帮助记住其参数顺序的方法是短语"将第一个元素插入(insert)到第二个之前(before)"。我将会给你一个在一分钟之内记住这个的更容易的方式。
  既然你拥有了一个在其它节点前面插入节点的函数,你应该问你自己了:“那我怎样把节点作为最后一个子节点插入呢?”有一个叫做appendChild的另一个函数正好让你那么做。在一个元素上调用appendChild,将把指定的节点追加到元素的子节点列表的末尾。使用些函数的大致代码如下:

parentElem.appendChild( nodeToInsert );

  为了避免记忆insertBefore和appendChild的特殊的参数顺序,你可以使用我创建的用来解决这一问题的两个辅助函数:使用5-23和5-24中的新的函数,参数的顺序总是先是与你要插入的元素/节点相关的元素然后是你要插入的元素/节点。另外,before函数允许你有选择性地提供父元素,可能为你省去一些代码。最后,这两个函数都允许你传入一个将被插入/追加的字符串并为你将它自动地转换成一个文本节点。推荐传递一个父元素作为引用(以防elem参数碰巧是null)。

  程序5-23. 在另一个元素前面插入元素的函数

function before( parent, before, elem ) {
    //检查是否提供了父节点
    if ( elem == null ) {
        elem = before;
        before = parent;
        parent = before.parentNode;
    }
    parent.insertBefore( checkElem( elem ), before );
}

  程序5-24. 将元素作为另一元素的子元素追加的一个函数

function append( parent, elem ) {
    parent.appendChild( checkElem( elem ) );
}

  程序5-25所示的辅助函数允许你容易地插入元素和文本(文本将自动地转换成合适的文本节点)。

  程序5-25. 用于before()和append()函数的一个辅助函数

function checkElem( elem ) {
    //如果提供了一个字符串,则将其转换为文本节点
    return elem && elem.constructor == String ?
        document.createTextNode( elem ) : elem;
}

  现在,使用before和append函数和创建新的DOM元素,你可以向DOM加入更多的信息供用户浏览,如程序5-26所示。

  程序5-26. 使用append和before函数

//新建一个<li>元素
var li = create("li");
attr( li, "class", "new" );
//创建新文本内容并将它加下到<li>元素
append( li, "Thanks for visiting!" );
//将<li>元素加入到第一个Ordered List的顶部
before( first( tag("ol")[0] ), li );
//运行以上语句将会把空的<ol>
<ol></ol>
//转换成如下:
<ol>
    <li class='new'>Thanks for visiting!</li>
</ol>

  在你将些信息插入到DOM的瞬间(不论使用insertBefore还是appendChild),它将立即被渲染并被用户看到。因此,你可以使用它提供即时的反馈。这在需要用户输入的交互式的应用程序中尤其有用。
  看过了怎样仅使用基于DOM的方法创建和插入节点,再来学习向DOM注入内容的替代方法将会是特别有益的。
   注入HTML到DOM

  甚至比创建一般的DOM节点并插入到DOM的更加流行的技术是直接将HTML注入到文档里。最简单的达到这一点的方法是使用前面讨论过的innerHTML属性。它除了作为一种检索元素内部的HTML的方法之外,也能用来设置元素内部的HTML。作为其简单性的一个例子,假设你有一个空的<ol>元素并且你想要给它加入一些<li>元素;完成这一任务的代码将是像这样的:

//往一个<ol>元素中加入一些<li>元素
tag("ol")[0].innerHTML = "<li>Cats.</li><li>Dogs.</li><li>Mice.</li>";

  这岂不是要比别着了魔似地创建一大堆地DOM元素和相关的文本节点要简单得多?你将会很高兴地知道它也比使用DOM方法要快得多(参见 http://www.quirksmode.org)。不过,它也并不是完美的——使用innerHTML注入的方法同样存在一些棘手的问题:
  1. 正如前面所提到的,innerHTML方法不存在于XML DOM文档中,这意味着你将不得不继续使用传统的DOM方法。
  2. 用客户端XSLT生成的XHTML文档不具有innerHTML方法,因为它们同样是纯XML文档。
  3. 设置innerHTML会完全删除原来已经存在于元素中的节点,这就是说没有办法像纯DOM方法里那样方便地追加或在前面插入。
  最后一点尤其麻烦,因为在其它元素之前插入或在子节点列表的后面追加是特别有用的一个功能。但是,借助一些DOM的魔力,你可以让你的append和before方法在常规的DOM元素之外也适用于常规的HTML字符串。这一转换分为两步。首先,你创建一个能够处理HTML字符串、DOM元素、以及DOM元素数组的新的checkElem函数,如程序5-27所示。

  程序5-27. 将混合了DOM节点和HTML字符串的数组参数转换成一个纯DOM节点数组

function checkElem(a) {
    var r = [];
    //如果参数不是一个数组,强迫它是
    if ( a.constructor != Array ) a = [ a ];
    for ( var i = 0; i < a.length; i++ ) {
        //如果是一个字符串
        if ( a[i].constructor == String ) {
            //创建一个临时的元素来储藏HTML
            var div = document.createElement("div");
        
            //注入HTML,将它转换成一个DOM结构
            div.innerHTML = a[i];
            
            //从临时<div>元素中取出DOM结构
            for ( var j = 0; j < div.childNodes.length; j++ )
                r[r.length] = div.childNodes[j];
        } else if ( a[i].length ) { //如果它是一个数组
            //假定它是DOM节点的数组
            for ( var j = 0; j < a[i].length; j++ )
                r[r.length] = a[i][j];
        } else { //否则,假定它是一个DOM节点
            r[r.length] = a[i];
        }
    }
    return r;
}

  第二,你需要使那两个插入函数适于与修改后的checkElem协同工作,接受元素的数组,如程序5-28所示。

程序5-28. 加强了的用于向DOM插入和追加内容的函数

function before( parent, before, elem ) {
    //检查是否提供了父节点
    if ( elem == null ) {
        elem = before;
        before = parent;
        parent = before.parentNode;
    }
    //取得新的节点数组
    var elems = checkElem( elem );
    //倒序遍历数组,
    //因为我们正在往前插入元素
    for ( var i = elems.length - 1; i >= 0; i-- ) {
        parent.insertBefore( elems[i], before );
    }
}
function append( parent, elem ) {
    //得到元素数组
    var elems = checkElem( elem );
    //将它们全部追加给元素
    for ( var i = 0; i <= elems.length; i++ ) {
        parent.appendChild( elems[i] );
    }
}

  现在,使用这些新函数,追加一个<li>元素给一个有序列表将变成一件极其简单的任务:

append( tag("ol")[0], "<li>Mouse trap.</li>" );
//运行上面简单的一行代码将会给此<ol>元素追加HTML:
<ol>
    <li>Cats.</li>
    <li>Dogs.</li>
    <li>Mice.</li>
</ol>
//将它变成下面这样:
<ol>
    <li>Cats.</li>
    <li>Dogs.</li>
    <li>Mice.</li>
    <li>Mouse trap.</li>
</ol>
//而为before()函数运行一个相似的语句
before( last( tag("ol")[0] ), "<li>Zebra.</li>" );
//则会将原来的<ol>变成:
<ol>
    <li>Cats.</li>
    <li>Dogs.</li>
    <li>Zebra.</li>
    <li>Mice.</li>
</ol>

  这的确有助于使你的代码更加简明清晰,利于开发。然而,要是你想走到上另一条道路,从DOM里删除节点呢?跟往常一样,也有处理这一问题的方法。

   从DOM中删除节点

  从DOM中删除结点几乎跟对应的创建和插入一样频繁。例如当为询问不限数目的项而动态地创建表单时,允许用户从页面中删除它们不想再处理的部分变得非常重要。删除节点的能力被压缩在一个函数中:removeChild。它像appendChild一样使用,但是具有相反的效果。该函数看起来是这个样子的:

NodeParent.removeChild( NodeToRemove );

  你可以创建两个独立的函数来快速地删除节点,如程序5-29所示。

  程序5-29. 从DOM中删除一个节点的函数

//从DOM中删除单个节点
function remove( elem ) {
    if ( elem ) elem.parentNode.removeChild( elem );
}

  程序5-30展示了一个函数用来删除一个元素的所有子节点,只使用对DOM元素的一个引用。

  程序5-30. 删除一个元素的所有子结点的函数

//从DOM中删除一个元素的所有子结点
function empty( elem ) {
    while ( elem.firstChild )
        remove( elem.firstChild );
}提示

  作为一个例子,我们假设你要删除你在上一节里添加的<li>元素;此前你已经给了用户足够的时间的来浏览<li>元素现在可以不加提示地删除。下面的代码展示了你可以用来执行这种行动的JavaScript代码,得到你想到的结果:

//从<ol>元素中删除最后一个<li>
remove( last( tag("ol")[0] ) )
//以上语句将会把
<ol>
<li>Learn Javascript.</li>
<li>???</li>
<li>Profit!</li>
</ol>
//转换成:
<ol>
<li>Learn Javascript.</li>
<li>???</li>
</ol>
//如果我们用empty()代替remove()
empty( last( tag("ol")[0] ) )
//它将简单地清空<ol>,剩下:
<ol></ol>
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值