第7章 利用 YUI 库遍历和操作 DOM (一)

来源:互联网 发布:费曼 知乎 编辑:程序博客网 时间:2024/04/29 06:47

本章内容简介:

  • 遍历 DOM 以及查找元素
  • 操作内容
  • 处理 CSS 和页面样式

7.1 遍历 DOM 以及查找元素

在遍历 DOM 时可以找到如下的元素。

7.1.1 get 方法

可以将其视为 getElementById 方法,但是功能更强大。DOM方法 getElementById 工作得很好,但是它欠缺一些灵活性。顾名思义,getElementById 方法只附带一个参数,也就是一个表示元素 ID 的字符串。而 get 方法将 getElementById 方法包装起来,可以传入一个字符串 ID 、HTMLElement 或者一个包含这两种参数之一的数组。如果接收到的是字符串ID,那么它执行简单的 getElementById 方法,并返回找到的第一个元素。如果接收到的是 ID 数组,那么它返回匹配这些 ID 的 HTMLElement 所构成的数组。如果遇到元素作为参数,那么它只是立即返回同样的元素。
当这个参数是传递给构造函数的参数时,最后一种行为非常有用,它具有一定的灵活性,可以从外部接收参数。
下面是一个示例:
function MyConstructor(id){
this.el = YAHOO.util.Dom.get(id);
};

var obj1 = new MyConstructor("foo");

var obj2 = new MyConstructor(document.body);

MyConstructor 函数的第一个实例将一个 ID 为 foo 的元素存储起来。第二个实例将 body 元素传给构造函数,然后将其传给 get 方法。get 方法识别出它是一个 HTMLElement 对象,只是将其立即返回。因此,MyConstructor 函数的第二个实例将 body 元素存储到它的 el 属性中。这就是 get 方法所提供的灵活性。

如果需要返回一组 HTMLElement 对象,那么还可以为 get 方法传入一个 ID 数组。在某些批处理操作中,如果希望确定某个数组的内容全部是 HTML 元素,那么这个行为非常有用。同样,get方法会在所有的字符串上执行 getElementById 方法,并为每个字符串返回一个元素。因此,下面这段代码的运行结果就是一个含有3个元素的数组 arr,第一个参数实际上会被忽略,原因就在于它已经是一个元素。

var foo = document.getElementById("foo");

var arr = YAHOO.util.Dom.get([foo, "bar", "baz"]);

7.1.2 getElementsByClassName 方法

W3C DOM 规范中一个较为显而易见的疏漏之处是没有一个通过类名来获取元素的方法。既然有了 getElementById、getElementsByTagName 甚至 getElementsByName 方法,那么人们理所当然地认为还应该有一个 getElementsByName 方法,但是并没有这样的方法。因此,YUI提供了它自己的 getElementsByClassName 方法。它的功能与 getElementsByTagName 方法类似,不仅可以在 document 元素上调用它,而且可以在任何其他元素上调用,这使得它成为搜索的起点。因此,下面的代码示例都是有效的:

var foo = YAHOO.util.Dom.getElementsByClassName("foo");

var bar = document.getElementById("bar");

bar baz = YAHOO.util.Dom.getElementsByClassName("baz", null, bar);

既然这是一种 JavaScript 解决方法,而不是浏览器本身的方法,那么需要考虑它的运行速度。为了达到快速运行的目的,还有第三个参数可供使用。传入正在搜索的节点类型,例如:

var foo = YAHOO.util.Dom.getElementsByClassName("foo", "a");

这个示例获取文档中所有具有类名 foo 的锚点元素。由于 getElementsByClassName 方法的构造方式的缘故,必须进行这种优化。它在内部执行 getElementsByTagName(*)方法 (这会匹配所有的元素),然后遍历每个元素来查找指定的类名。如果给定了一个节点名称,那么它需要遍历的节点集合将迅速变小,从而获得更佳的性能。

最后一个参数可用来在找到的每个节点上执行一个函数。与其再编写一个循环对返回的元素集合应用一个函数,不如利用目前这个用于查找这些元素的循环。这个附加的参数的作用同样也是为了优化性能。通过使用这个参数,就不再需要第二个循环,这在较大的节点集合中可以获得性能上的显著提升。

下面是一个演示如何使用该方法的示例:

function addClick(el) {

el.onclick = function(){

alert("click");

};

}

var nodes = YAHOO.util.Dom.getElementsByClassName("foo", "a", document, addClick);

在内部,唯一传给 addClick 函数的参数是循环中的当前元素 (由变量名 el 表示)。

7.1.3 getFirstChild 和 getLastChild 方法

一个用来说明跨浏览器特性的优秀示例就是 DOM 方法 firstChild 。

在 Internet Explorer 中,它返回的是给定元素的属于 HTMLElement 类型的第一个子节点。

而在 Firefox、Safari 和 Opera 浏览器中,情况并不是这样。在标准模式中,这些浏览器均严格遵循 W3C 规范,返回给定元素内的第一个文本节点。这是因为(根据W3C规范)DOM中每个类型为 HTMLElement 的节点(也就是HTML标记)在其任意一侧都有一个空白的文本节点作为其兄弟节点。

这是一个比较麻烦的问题,因为当需要第一个子元素时,所需要的总是 HTMLElement 而不是文本节点。getFirstChild 方法确保总是返回 HTMLElement 类型的节点,从而将这种行为规范化。也就是说,它跳过了第一个文本节点。

下面的示例演示了该方法的用法:

<html>

<head><title> YUI getFirstChild / getLastChild Test </title></head>

<body>

<p>Hello World</p>

<p>This is not the first child.</p>

<p>Neither is this.</p>

<script src="yahoo-dom-event.js"></script>

<script>

var helloWorld = YAHOO.util.Dom.getFirstChild(document.body);

var lastScript = YAHOO.util.Dom.getLastChild(document.body);

</script>

</body>

</html>

这段代码将返回在文档的正文中找到的第一个 HMTLElement 。在这里,它就是包含文本 "Hello World" 的段落元素。

类似地,getLastChild 方法确保在 Firefox 中返回倒数第二个节点,从而将关于跨浏览器文本节点的古怪行为规范化。在上面的示例中,最后一个子节点刚好是包含该示例的 JavaScript 代码的 script 标记。因此,这个变量的名称为 lastScript 。

7.1.4 getFirstChildBy 和 getLastChildBy 方法

有时我们需要的节点并不是节点集合中的第一个或最后一个节点,而是节点集合中特定类型的第一个或最后一个节点。但是,没有 W3C DOM 方法可用于这种节点检索形式。请不要担心,YUI DOM 集合刚好实现了这些方法。而且,YUI DOM集合并没有创建特定于某些条件的方法 (例如 getElementById) ,它将条件的确定权交给程序员。实现这种灵活性的就是下面这两个方法:getFirstChildBy 和 getLastChildBy 方法。这两个方法都附带一个起始节点作为它们的第一个参数,并且附带一个函数作为第二个参数。这个函数接收一个元素作为参数,并且必须返回 true 或 false 。下面是这些方法的实际用法:

<html>

<head><title>YUI getFirstChildBy / getLastChildBy Test</title></head>

<body>

<p>Hello World</p>

<p class="intro">Hello Planet! </p>

<p class="intro">This is not the first child.</p>

<p>Neither is this.</p>

<p class="outtro">This one is somewhere in the middle.</p>

<p class="outtro">So is this.</p>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.intro = YD.getFirstChildBy(document.body, 

function(el){

return (el.className === "intro");

});

YX.outtro = YD.getLastChildBy(document.body,

function(el){

return (el.className === "outtro");

});

</script>

</body>

</html>

在上面的示例中,intro 变量存放着含有文本 "Hello Planet!" 的段落元素,而 outtro 变量则包含带有文本 "So is this." 的段落元素。这些段落元素均不是 body 元素的第一个子节点或最后一个子节点。但是,它们都是各自所属节点类型的第一个子节点和最后一个子节点。

7.1.5 getChildren 和 getChildrenBy 方法

通常,在处理 DOM 时,正在处理的节点的期望类型是元素节点。但是,document.body.childNodes 方法会返回文本节点、注释节点以及所有其他类型的节点。这可能是一件麻烦的事情,因为在处理最终的结合时还需要额外的步骤来过滤元素节点之外的所有节点。但是,getChildren 方法会针对元素节点预先过滤结果。它还会返回一个数组,这是一种重要的行为,因为原生的 DOM 方法 childNodes 不会这样做。该方法看似会这样做,但是它实际上返回一个 NodeList,该 NodeList 类似于一个数组,但是不具备数组的几个方法。因此,尽管可以通过 for 循环遍历该 NodeList ,但是不能对其执行相应的操作。

不仅 getChildren 方法会针对元素节点进行过滤,而且它的相关方法 getChildrenBy 也可以通过一个用户自定义的 Boolean 函数来执行进一步的过滤。

<html>

<head><title>getChildrenBy</tile></head>

<body>

<p id="foo">Lorem ipsum dolor sit <em>amet</em>, consectetuer adipiscing elit.<a href="/vivamus/">Vivamus</a> sed nulla. <em>Donec</em>vitae pede.

<strong>Nunc</strong> dignissim rutrum nisi.</p>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.isEm = function (el){

if(el.nodeName.toUpperCase() === "EM"){

return true;

}

return false;

};

YX.init = function(){

var foo = YD.get("foo");

var ems = YD.getChildrenBy(foo, YX.isEm);

var msg = "";

for(var i=0; ems[i]; i+=1){

var em = ems[i];

msg += "<em>" + (em.innerHTML || em.textContent) + "</em>, ";

}

msg = msg.substring(0, msg.length - 2);

alert("The following elements are emphasized: " + msg);

}();

</script>

</body>

</html>

上面的示例获取 ID 为 foo 的段落元素的所有 em 子元素。isEm 函数可用作 Boolean 过滤器,它接收一个元素作为参数,检查它的节点名称是否为 "EM",然后返回 true 或 false。一旦找到所有 em 元素,就会显示一条警告消息来列出匹配的元素。

7.1.6 getElementsBy 方法

YUI DOM 集合还提供了一个 getElementsBy 方法,它为执行自定义执行了很大的便利。getElementsBy 方法并不限制特定类型的过滤 (也就是 "按照类名"、"按照ID"),而是将过滤条件交给开发人员确定。getElementsBy 方法接收的第一个参数是一个 Boolean 函数,将待过滤的元素传给这个 Boolean 函数进行测试。如果该元素通过测试 (即该函数返回 true) ,那么它将这个元素存储到最终返回的元素数组中。如果该元素未能通过测试,那么它就会移向移向下一个元素。此外,与 getElementsByClassName 方法类似,可以指定一种节点类型和一个根节点作为起点。最后,可以传入一个函数,该函数会应用于通过 Boolean 函数测试的每个节点。

<html>

<head><title>getElementsBy</title></head>

<body>

<ul id="nav">

<li><a href="/" >Home</a></li>

<li>

<a href="/products/">Products</a>

<ul><li><a href="/products/widget/">Widget</a></li><li><a href="/products/gadget/">Gadget</a></li><li><a href="/products/whatzit/">Whatzit</a></li></ul>

</li>

<li>

<a href="/partners/">Partners</a>

<ul>

<li><a href="http://www.widgetsinc.com">Widget Inc</a></li>

<li><a href="http://www.gadgetsinc.com">Gadgets Inc</a></li>

<li><a href="http://www.whatzitsinc.com">Whatzits Inc</a></li>

</ul>

</li>

<li><a href="/about/">About Us</a></li>

<li><a href="/contact/">Contact</a></li>

</ul>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.isExternal = function(el){

if(el.href.substring(0,4) === "http"){

return true;

}

return false;

};

YX.makePopup = function(el){

el.onclick = function(){

window.open(el.href);

return false;

};

};


YX.externalLinks = YD.getElementsBy(YX.isExternal, "a", "nav", YX.makePopup);

</script>

</body>

</html>

上面的示例将函数 isExternal 应用于元素内 ID 为 nav 的所有锚点节点。然后将函数 makePopup 依次应用于通过这个测试的每个元素。这段代码的运行结果不言自明;其 href 特性以 "http" 开头的任何链接都会在新窗口中打开。

7.1.7 getAncestorByTagName 方法

有时候,充当容器的父元素需要受到在它的一个子元素上执行的操作的影响。但是麻烦的地方在于,子元素并不知道容器距离自己有多远,因此无法只是使用 parentNode 指针来指向该容器。

var parent = this.parentNode;

实际上,这里需要的操作是沿着 DOM 向上遍历,直到找到正在查找的节点。这正是 getAncestor 系列方法发挥作用的地方。 getAncestorByTagName 方法从一个给定的节点开始向上沿着 DOM 遍历,并找到具有指定标记名称的父节点 (即祖先节点)。

<html>

<head><title> getAncestorByTagName </title></head>

<body>

<ul id="nav">

<li><a href="/" id="home"> Home </a></li>

<li><a href="/contact/" id="contact"> Contact </a></li>

<li><a href="/about/" id="about"> About Us</a></li>

</ul>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.contact = document.getElementById("contact");

YX.contact.onmouseover = function(){

var ancestor = YD.getAncestorByTagName(this, "ul");

if(ancestor){

ancestor.style.border = "solid 1px red";

}

};

YX.contact.onmouseout = function(){

var ancestor = YD.getAncestorByTagName(this, "ul");

if(ancestor){

ancestor.style.border = "none";

}

};

</script>

</body>

</html>

在这个示例中,ID 为 contact 的锚点节点有两个事件处理程序,分别是 onmouseover 和 onmouseout ,它们均调用 getAncestorByTagName 方法,并将该锚点节点传入作为起点。它们还规定应该在遇到第一个 UL 父节点时停止遍历。一旦找到该元素 (即祖先节点) ,就通过 style 对象来修改它的边框。

7.1.8 getAncestorByClassName 方法

类似地,getAncestorByClassName 方法从一个给定的节点开始,沿着 DOM 向上搜索具有给定类名的父元素。

<html>

<head>

<title>getAncestorByClassName</title>

<style>

.main {

border: solid 1px white;

}

</style>

</head>

<body>

<div class="main">

<div class="header">

<ul id="nav"><li><a href="/" id="home">Home</a></li><li><a href="/contact/" id="contact">Contact</a></li><li><a href="/about" id="about">About Us</a></li></ul>

</div>

<div class="body">

<p>The page's main content block goes here</p>

</div>

<div class="footer">

<p><small>The page's footer goes here</small></p>

</div>

</div>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.contact = document.getElementById("contact");

YX.contact.onmouseover = function(){

var ancestor = YD.getAncestorByClassName(this, "main");

if(ancestor){

ancestor.style.border = "solid 1px red";

}

};

YX.contact.onmouseout = function (){

var ancestor = YD.getAncestorByClassName(this, "main");

if(ancestor){

ancestor.style.border = "solid 1px white";

}

};

</script>

</body>

</html>

这是一个与 getAncestorByTagName 类似的示例。但是在该示例中,当光标悬停在 contact 链接上时,就会把最外围的、ID 为main 的div 节点作为目标,并通过 style 对象修改它的边框。

7.1.9 getAncestorBy 方法

最后,使用 getAncestorBy 方法可以按照任何能够想到的条件来定位祖先节点,只需要将该条件编写进一个函数即可。该函数需要接收一个元素作为参数,然后针对该元素进行计算,并返回 true 或 false 。因此,在 getAncestorBy 方法从它的起点开始向上遍历 DOM 的过程中,它会将遇到的每个父元素传给这个 Boolean 函数进行计算。如果该函数返回 false ,它就会继续移向链式结构中的下一个元素。如果该函数返回 true ,它就会停止遍历将该元素返回给它的调用程序。

<html>

<head><title> getAncestorBy </title></head>

<body>

<div class="main">

<div class="header">

<ul id="nav">

<li><a href="/" id="home"> Home </a></li><li><a href="/contact" id="contact"> Contact </a></li><li><a href="/about" id="about"> About Us </a></li>

</ul>

</div>

<div class="body">

<p>The page's main content block goes here</p>

</div>

<div class="footer">

<p><small>The page's footer goes here</small></p>

</div>

</div>

<script>

var YD = YAHOO.util.Dom, YX = YAHOO.example;

YX.hasBGColor = function(el){

if(el.nodeName.toUpperCase() === "DIV" && el.style.backgroundColor) {

return true;

}

return false;

};

YX.hasNoBGColor = function(el){

if(el.nodeName.toUpperCase() === "DIV" && !el.style.backgroundColor){

return true;

}

return false;

};

YX.contact = document.getElementById("contact");

YX.contact.onmouseover = function(){

var ancestor = YD.getAncestorBy(this, YX.hasNoBGColor);

if(ancestor){

ancestor.style.backgroundColor = "red";

}

};

YX.contact.onmouseout = function(){

var ancestor = YD.getAncestorBy(this, YX.hasBGColor);

if(ancestor){

ancestor.style.backgroundColor = "";

}

};

</script>

</body>

</html>

在这里,光标在 contact 链接上悬停或者离开时都会调用 getAncestorBy 方法,并分别传入 Boolean 函数 hasNoBGColor 和 hasBGColor 。hasNoBGColor 函数查找第一个没有设置背景色的 div 祖先节点。然后,onmouseover 事件处理程序将该节点的背景设为红色。类似地,hasBGColor 函数查找第一个设置了背景色的 div 祖先节点。然后,onmouseout 事件处理程序将该节点的背景色清除。

原创粉丝点击