2.6 DOM遍历方法

2.6 DOM遍历方法

利用前面介绍的jQuery选择符取得一组元素,就像是我们在DOM树中纵横遍历再经过筛选得到的结果一样。很多情况下,取得某个元素的父元素或者祖先元素都是基本的操作,而这正是jQueryDOM遍历方法的用武之地。有了这些方法,我们可以轻而易举地在DOM树中上下左右地自由漫步。
其中一些方法与选择符表达式有异曲同工之妙。例如,这行用于添加alt类的代码:

1
$('tr:even').addClass('alt');

可以通过.filter()方法重写成下面这样:

1
$('tr').filter(':even').addClass('alt');

而且,这两种取得元素的方式在很大程度上可以互补。同样, .filter()的功能也十分强大,因为它可以接受函数参数。通过传入的函数,可以执行复杂的测试,以决定相应元素是否应该保留在匹配的集合中。例如,假设我们要为所有外部链接添加一个类。

1
2
3
4
a.external { 
background: #fff url(images/external.png) no-repeat 100% 2px;
padding-right: 16px;
}

jQuery中没有针对这种需求的选择符。如果没有筛选函数,就必须显式地遍历每个元素,对它们单独进行测试。但是,有了下面的筛选函数,就仍然可以利用jQuery的隐式迭代能力,保持代码的简洁,如代码清单2-9所示。

1
2
3
$('a').filter(function() { 
return this.hostname && this.hostname != location.hostname;
}).addClass('external');

第二行代码可以筛选出符合下面两个条件的<a>元素。
必须包含一个带有域名(this.hostname)的href属性。这个测试可以排除mailto及类似链接。
链接指向的域名(还是this.hostname)必须不等于(!=)页面当前所在域的名称(location.hostname)。

更准确地说,.filter()方法会迭代所有匹配的元素,对每个元素都调用传入的函数并测试函数的返回值。如果函数返回false,则从匹配集合中删除相应元素;如果返回true,则保留相应元素。
有了这些代码,Henry V就被标记为外链了,如图2-12所示。
下面,我们再通过前面添加了条纹效果的表格,来演示一些遍历方法的其他用途。

2.6.1 为特定单元格添加样式

此前,我们已经为所有包含文本Henry的单元格添加了highlight类。如果想改为给每个包含Henry的单元格的下一个单元格添加样式,可以将已经编写好的选择符作为起点,然后连缀一个.next()方法即可,参见代码清单2-10。

1
2
3
$(document).ready(function() { 
$('td:contains(Henry)').next().addClass('highlight');
});

表格现在的效果如图2-13所示。

图2-13

.next()方法只选择下一个最接近的同辈元素。要想突出显示Henry所在单元格后面的全部单元格,可以使用.nextAll()方法,如代码清单2-11所示。

1
2
3
$(document).ready(function() { 
$('td:contains(Henry)').nextAll().addClass('highlight');
});

因为包含Henry的单元格位于表格的第一列,因此以上代码会导致相应行中的其他单元格突出显示,如图2-14所示。

有读者可能已经猜到了,.next().nextAll()方法分别有一个对应方法,即.prev().prevAll()。此外,.siblings()能够选择处于相同DOM层次的所有其他元素,无论这些元素处于当前元素之前还是之后。
要在这些单元格中再包含原来的单元格(即包含Henry的那个单元格),可以添加.addBack()方法,参见代码清单2-12。

1
2
3
4
$(document).ready(function() { 
$('td:contains(Henry)').nextAll().addBack()
.addClass('highlight');
});

作了这个修改之后,相应行中的所有单元格就都会应用highlight类的样式了,如图2-15所示。
事实上,要选择同一组元素,可以采用的选择符和遍历方法的组合很多。 例如, 代码清单2-13就是选择所有包含Henry的单元格所在行的另一种方式。

1
2
3
4
$(document).ready(function() { 
$('td:contains(Henry)').parent().children()
.addClass('highlight');
});

这种组合方式没有遍历同辈元素,而是通过.parent()方法在DOM中上溯一层到达<tr>,然后再通过.children()选择该行的所有单元格。

2.6.2 连缀

刚刚介绍的遍历方法组合展示了jQuery的连缀能力。在jQuery中,可以通过一行代码取得多个元素集合并对这些元素集合执行多次操作。jQuery的这种连缀能力不仅有助于保持代码简洁,而且在替代组合重新指定选择符时,还有助于提升脚本性能。
方法连缀的原理:
几乎所有jQuery方法都会返回一个jQuery对象,因而可连缀调用多个jQuery方法。
在使用连缀时,为照顾到代码的可读性,还可以把一行代码分散到几行来写。例如,一组连缀的方法可以写成3行,参见代码清单2-14。

1
2
3
$('td:contains(Henry)').parent().find('td:eq(1)') 
.addClass('highlight').end().find('td:eq(2)')
.addClass('highlight');

甚至,也可以写成7行,参见代码清单2-15。

1
2
3
4
5
6
7
$('td:contains(Henry)') //取得包含Henry的所有单元格 
.parent() //取得它的父元素
.find('td:eq(1)') //在父元素中查找第2个单元格
.addClass('highlight') //为该单元格添加hightlight类
.end() //恢复到包含Henry的单元格的父元素
.find('td:eq(2)') //在父元素中查找第3个单元格
.addClass('highlight'); //为该单元格添加hightlight类

不可否认,这个例子中展示的迂回曲折的DOM遍历过程几近荒谬。我们当然不建议读者使用如此复杂的连缀方式,因为还有更简单、更直接的方法。这个例子的用意只是演示一下连缀为我们带来的极大灵活性。
连缀就像是一口气说出一大段话——虽然效率很高,但对别人来说可能会难于理解。而将它分开放到多行并添加明确的注释,从长远来看则可以节省更多的时间。