简介: 在 Web 应用程序开发中,JavaScript 的应用越来越普遍,越来越复杂,一个 Web 页面中往往有成百上千个 HTML 元素,准确、高效地选择所需的元素并对其进行操作,不仅可以在程序开发阶段节省编码时间,降低程序出错的概率,在运行还能提高程序运行效率,提供更好的用户体验。Dojo 提供了功能强大的 Query 函数库,使用一个高效的查询引擎,能够根据元素 id,名称,CSS,属性及其组合对页面的元素进行查询,并且能够对返回结果进行非常方便的处理。本文详细介绍 dojo.query 的各种查询方式,以及常用对结果的处理方式。恰当使用 dojo.query 能够大大提高程序开发的效率,减少代码量,提高编码质量。
Dojo Query 简介
在 Web 开发中,我们经常需要根据一定的条件查询并获取某些 HTML 元素的引用,然后对这些元素进行操作以改变它们的属性、外观等。在标准的 JavaScript 语言中,可以通过 document.getElementById, document.getElementsByTagName, document.getElementsByClassName 等函数对 DOM 树进行查询,然后遍历返回结果分别进行处理。在一些复杂的情况下,比如要查询 id 为“id1”的 div 元素下所有 class 为“class1”并且包含属性“att1”的元素,使用标准的 JavaScript 查询就显得力不从心,即使能够实现其代码也显得纷繁冗长。另一方面,在得到查询结果以后,假如要将所有返回的元素的 class 设置为“class2”,则需要遍历结果数组,一一进行设置。使用 JavaScript 标准的查询方式功能有限,使用复杂,而且当页面元素过多时还会导致性能降低。
Dojo Query 库提供了一种简洁而强大的查询语法,以及对返回结果简便的处理方式。Dojo Query 库的核心是一个 dojo.query 函数,该函数接收一个查询字符串,以及一个可选的 DOM 节点作为参数,返回一个 NodeList 对象。一方面我们可以通过 id,元素名称,属性,CSS 等及其组合设置精巧的查询字符串准确控制返回的结果,另一方面返回的 NodeList 对象提供了丰富的操作接口,对其调用的很多方法(如 addClass)都可以直接作用于所有的元素,并且支持链式调用。如下面的例子:
dojo.query("div.someClassName").style("backgroundColor","gray").forEach("item.disabled= true;");
首先在 DOM 根结点查询 class 为“someClassName”的 div 元素,对于返回的所有元素,将 style 属性“backgroundColor”设置为“gray”,然后将元素的“disabled”属性设置为“true”。使用 Dojo Query 能够精确地控制查询结果并方便地对所有结果进行操作,其使用非常简便,代码精炼,可读性强。本文后续章节将详细介绍查询语法及常见使用场景,以及 NodeList 对象的使用。
回页首
基于 Dojo Query 的查询
当我们采用 Dojo Query 进行查询时,通常使用最多的就是根据 HTML 元素的 tag 名称,或是 class 以及 id 属性来进行查询。这也是我们在引用 CSS 元素进行选择时常用的几种方式。比如,假设我们想查询页面内所有的 <img> 标签,那么不妨可以这么写:
dojo.query("img");
此处,img 即为标签名称,由于 Dojo Query 并不对大小写进行区分,故而形如 <IMG> 这样的 HTML 元素也会被包含在查询结果之中。上述语法的执行效果,等价于在 JavaScript 中调用 DOM API:document.getElementsByTagName("IMG")。
同样,根据 class 和 id 属性进行查询的语法,也很简单。例如,下面的例子实现的是在当前页面中查询 id 为 widget123 的元素:
dojo.query("#widget123");
其执行效果,等价于调用 document.getElementById("widget123");,或者,利用 Dojo 提供的 API,则相当于 dojo.byId("widget123")。
针对 class 属性的查询同样的简单直观,例如,下面的例子是查找所有 class 名称为“offToSeeTheWij”的 HTML 元素:
dojo.query(".offToSeeTheWij");
此处我们可以看到,同样的功能,如果用 DOM API 来实现,则将会变得非常冗长乏味:
清单 1 DOM API 查询
var list = []; var nodes = document.getElementsByTagName("*"); for(var x = 0; x < nodes.length; x++){ if(nodes[x].className == "progressIndicator"){ list.push(nodes[x]); } }
在上述代码中,我们需要对页面中的所有节点进行遍历,逐一判断其 className 属性是否满足匹配条件才行。通过这个典型的例子,我们可以清楚地看到,使用 Dojo Query 在 DOM 节点查询方面具有非常显著的优势。
事实上,使用 Dojo Query 不仅在表达查询条件的简洁性上会更具优势,相比于直接利用 DOM API 进行处理的方式,其执行速度也通常会更加的快。接下来读者便会看到,这一点尤其在我们需要查询相对复杂的页面节点关系时,会变得更为突出。
除了上述我们看到的,Dojo Query 提供了基本的依据 tag、class、id 进行查询的方式以外,它还提供了许多更为复杂的查询语法,而所有这些语法则都遵循于 CSS 规范。Dojo Query 的这一做法十分的明智,因为 CSS 是已经被大家所广泛使用和接受了的一种 Web 技术,其对 HTML 页面元素进行选择性修饰的方式,兼具语法简洁和功能强大的特点,目前所有的主流浏览器都对 CSS 有很好的支持。Dojo Query 沿用了 CSS 的语法,使得使用者无需学习一整套新的查询语法,便可以很好的掌握 Dojo Query 的使用,以完成各种复杂的查询功能。
目前,Dojo Query 支持许多常见的 CSS 选择器语法。例如,我们可以利用较为复杂的类选择器语法,实现对符合指定 class 属性的指定元素进行查询:
dojo.query("div.someClassName");
又比如,我们可以将对各 tag 名称结合起来实现组合查询,下面完成的即是查询出所有的 h1,h2,h3 节点:
dojo.query("h1,h2,h3");
除此以外,Dojo Query 还支持某些特殊的在 CSS 3 中定义的选择器。我们可以在查询条件中引用某些特殊的伪类选择符,比如可以利用 nth-child 来查询 tbody 节点下所有奇数序号的子元素:
dojo.query("tbody tr:nth-child(odd)");
利用 first-child 来查询任意节点下的首个 p 子元素:
dojo.query("p:first-child");
还可以利用诸如“^=”、“$=”、“*=”这样的属性选择器,实现匹配特定字符串条件的查询。例如,下列代码就是分别用来查询 name 属性的取值以“item”打头,以“item”结尾,和包含“item”字样的元素的:
清单 2 使用属性选择器
dojo.query("[name^=item]"); dojo.query("[name$=item]"); dojo.query("[name*=item]");
上面我们看到的有关于 Dojo Query 的例子都只接受一个参数,它们实现的是在全局范围内,即整个页面范围内,对节点进行查询。Dojo Query 还支持局部范围内的相对查询。此时,除了查询表达式外,我们需要传入另一个参数,用以指示查询起始的根节点。该参数可以是一个字符串,Dojo Query 会将其视作元素的 id 值;或者我们也可以传入一个 DOM 节点。在下面的例子里,我们实现了在 thisForm 这个节点下进行查询的功能:
清单 3 局部范围内的相对查询
<html> <head> <script type="text/javascript" src="../js/dojo/dojo.js"></script> <script type="text/javascript"> dojo.addOnLoad(function() { console.debug(dojo.query("button").length); // 输出"3" console.debug(dojo.query("button", "thisForm").length); // 输出”1” }); </script> </head> <body> <button id="b1" /> <button id="b2" /> <form id="thisForm" > <button id="formB" /> </form> </body> </html>
回页首
对查询结果进行后续操作
通过以上章节的介绍,我们知道,Dojo Query 返回的结果是 NodeList 对象。NodeList 是一个扩展的 Array 对象,它提供了丰富的操作接口方法。基本而言,NodeList 提供了几乎所有操作 DOM 的方法,且简单易用;因为它本身是 Array 对象,所以它支持所有的 Dojo 对数组的操作方法;同时,它也提供了很多直接处理事件的方法。而且,NodeList 还有一个显著的优点,就是很多方法支持链式调用。所谓链式调用,是指 NodeList 的方法调用之后仍会返回当前的对象,可以通过“.”级联继续应用其他的操作,例如 :
清单 4 链式调用
dojo.query(".thinger ").style {border :"1px" }).removeClass("thinger").addClass("thinger2");
我们先来看一下 NodeList 的基本操作。
作为 Array 对象,NodeList 具有长度属性,而且可以通过 at,forEache,push,pop 这些方法来操纵它。需要注意的是 at,map,forEach,slice,splice,contact 等都可以进行链式调用,但是 push,pop,shift 和 unshift 则是不可以的。请参看以下代码片段:
清单 5 NodeList 基本操作
var l = dojo.query(".thinger"); console.log("Size of items with class thinger:"+l.length); //NodeList 中加入对象 l.push(dojo.create('div', { innerHTML:'hi' })); console.log("Size of items with class thinger:" + l.length); l.push(dojo.byId("foo")); console.log("Size of items with class thinger:" + l.length); // 查询 id 为 foo 的元素在 NodeList 中的位置 console.log( l.indexOf(dojo.byId("foo")) ); // 获取第四个元素 var node = l[3]; // 通过 at 方法,一次找出第二和第四个元素,返回结果也是一个 NodeList。 var newList = l.at(1, 3);
在使用 NodeList 的过程中,如果大家细心的话,也许会注意到,NodeList 在封装 dojo.* 方法的同时,通过自描述(self-descriptive)方式省略掉了第一个参数,这也是符合我们使用习惯的。例如 NodeList 的 forEach 方法,其用法看起来很像 dojo.forEach(Array,Function)。不过第一个参数被隐式提供了,即将 NodeList 对象本身作为 Array 参数的值。
下面这个例子说明如何在 <div> 内添加内容。
清单 6 NodeList.forEach 方法
dojo.query("div").forEach(function(node, index, array){ node.innerHTML = "new version content!"; });
注意,通过自描述省略第一个参数的概念贯穿所有 NodeList 的 API。下面介绍的 NodeList 的常用方法,也适用上面的概念。
dojo.style 是一个应用于单个 node 的 API,这个 API 对应到 NodeList API,等价于隐式省去 node 参数,然后将其应用于 NodeList 中的每一个元素。
清单 7 NodeList.style 方法
// 执行查询
var borders = dojo.query(".thinger").style("border"); // 设置新值 dojo.query(".thinger").style("border", "1px solid black"); // 删除,添加 class dojo.query(".thinger").style({border :" 3px solid red" }).removeClass("thinger"). addClass("thinger2");
上面的代码段是一个 NodeList API 进行级联调用的例子。大部分 DOM 相关的对象都如上述例子一样,返回 NodeList,然后我们可以对其进行链式调用。但并非所有的 API 都可以级联,NodeList.coords 就是一个例外,它返回的是一组符合条件的坐标值,因此是不可以级联的。
var nl = dojo.query(".foo");
var coords = nl.coords(); // 例如返回对象 { x,y,z}
所以我们在使用 NodeList API 时,要注意并不是所有的 API 都可以级联调用。这个基本上可以通过是否返回 NodeList 对象进行判断。
现在,我们简单介绍一下,如何在 NodeList 中使用事件。
以 NodeList.connect() 为例,它提供了一种方法,可以向所有的 DOM Node 中添加事件。对应于 dojo.connect API,同样,node 参数对应于 NodeList 中当前的 Node。
清单 8 NodeList.connect 方法
dojo.query("input").connect("onclick", function(e){ alert("new event!"); });
我们可以像使用 dojo.connect 一样,向 NodeList.connect 添加任何事件。为了方便起见,NodeList 提供了很多 DOM 的直接操纵对应事件的方法,所以上面的例子也可以写成:
清单 9 NodeList.onclick 方法
dojo.query("input").onclick(function(e){ alert("new event!"); });
直接支持的事件还包括 onclick, onmouseenter, onmouseleave, onmouseover, omouseout, ondblclick 等。例如:
清单 10 NodeList 的鼠标事件
dojo.query("p").onmouseenter(function(e){ dojo.style(e.target, "color", "red"); }).onmouseleave(function(e){ dojo.style(e.target, "color", "blue"); });
有时,为了自己使用的方便或者出于兴趣,也许你想在 NodeList 中增加一些方法,以便可以在 dojo.query 的调用中使用这些方法。为此我们可以通过 dojo.extend 扩展 NodeList 向 NodeList 中添加自定义的新方法。如:
清单 11 扩展 NodeList 方法
dojo.extend(dojo.NodeList, { setColor: function(newColor){ this.style({ color: newColor }); return this; } }); dojo.query("p").setColor ("yellow");
注意,此处的关键是“return this”,它保证了后续链式调用的成功执行。
另外,为了保持 NodeList 基本操作函数的紧凑性,一些扩展方法是以外部包的形式提供的。例如,dojo.NodeList-fx 为 NodeList 引入了 FX/Animation,dojox.fx.ext-dojo.NodeList 提供 dojox.fx 里关于动画的功能,dojo.NodeList-html 提供了高级的 HTML 操纵功能,等等。
通过以上的介绍我们可以看出,通过 NodeList,我们可以更加简单地操纵 DOM 对象,更加方便地添加事件,更重要的是,我们还可以进行链式操作,以减少代码复杂度,加快开发速度。
回页首
小结
现在很多主流的 JavaScript 框架都提供了功能强大的 Dom 节点查询和操纵功能,使用这些框架的好处也是显而易见的,可以屏蔽浏览器差异,能够更为精确简便地查找节点,并提供对结果的集合操作来简化后续处理等等。本文介绍了 Dojo Query 的查询语法和使用场景以及如何对结果集合 NodeList 进行处理,在 Dojo 开发中恰当使用 Dojo Query 能够大大提高代码质量,缩短开发周期。
回页首