Selectors API - 详解 querySelectorAll 获取与遍历 DOM 元素列表

本文详细介绍了如何使用querySelectorAll方法结合CSS选择器获取文档中指定元素列表,并深入探讨了返回的NodeList对象的特性及如何将其转换为数组以便使用Array方法。

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

使用 document.querySelectorAll 搭配CSS选择器,可以非常便捷地获取文档中指定元素列表。

elementList = parentNode.querySelectorAll(selectors);
  • 参数 selectors:一个DOMString包含一个或多个匹配的选择器。
  • 返回值:一个静态NodeList(一个集合,不是数组,类似于数组,文本稍后做详解),包含一个与至少一个指定选择器匹配的元素的Element对象,或者在没有匹配的情况下为空NodeList。

(1)应用示例:

1、获取表格第二列中的所有单元格:

<table id="table01">
    <tr>
        <td>Merry</td><td>16</td>
    </tr>
    <tr>
        <td>Alice</td><td>17</td>
    </tr>
    <tr>
        <td>Penn</td><td>20</td>
    </tr>
</table>
//使用querySelectorAll找到第二列中所有单元格

//写法1:当文档中只有一个table时
var cells1 = document.querySelectorAll("td + td");

//写法2:指定table
//首先使用document.querySelector获取id为table01的node节点
var table1 = document.querySelector('#table01');

//使用element + element选择器-->td + td, 选择所有紧接着td元素之后的td元素
var cells2 = table1.querySelectorAll("td + td");

//使用:nth-of-type(n)选择器-->td:nth-of-type(2),选择每个td元素是其父级的第二个td元素
var cells3 = table1.querySelectorAll("td:nth-of-type(2)");

获取单元格中的值,通过firstChild.data即可获取:

for (var i = 0; i< cells.length; i++) {
     console.log(parseFloat(cells[i].firstChild.data))
}

事实上,我们可以通过打印cells,查看该node列表的所有属性:

(2)关于返回值:

返回值是一个静态NodeList(一个集合,不是数组,类似于数组),包含一个与至少一个指定选择器匹配的元素的Element对象,或者在没有匹配的情况下为空NodeList。NodeList不具备Array对象的方法,如果要对 NodeList 使用Array方法,如 forEach方法,必须将NodeList 转换成数组类型。

NodeList的唯一属性是length,其唯一的方法是 item(),它接受作为参数传入的一个索引,返回位于该位置的元素:

var td = cells.item(1) //cells中的第二个td

我们可以对 NodeList 使用for循环,但不能随意使用Array对象的 forEach()方法,使用forEach()是有条件的:)

for (var i = 0; i< cells.length; i++) {
     console.log(parseFloat(cells[i].firstChild.data))
}

如果我们想要把NodeList作为数组来使用,必须在调用数组方法的之前,先将其转换为数组类型。 

【 问题 】:想要对调用querySelectorAll() 所返回的一个nodeList,使用forEach()。

【 解决方案1 :直接在NodeList上使用 forEach() 】:

可以将forEach()强制地和一个NodeList (querySelectorAll()所返回的集合) 一起使用,代码如下:

var cells = document.querySelectorAll("td + td");
    
[].forEach.call(cells, function(cell){
     sum += parseFloat(cell.firstChild.data);
});

【 解析 】:

forEach()是一个Array方法,而 querySelectorAll() 的结果是一个NodeList,这是和Array 不同类型的一种对象。

在这个解决方案中,为了让 forEach() 和 NodeList 一起调用,我们在一个空的数组上调用了该方法,然后在该对象上使用call()方法,call() 的第一个参数 cells 将 forEach 方法的调用对象替换为cells,第二个参数 function(cell){...}函数 作为参数传递给forEach。这一技术所做的有效的事情是,迫使 cells 以一种可接受的格式(数组),充当 forEach() 的参数。从而在NodeList上模拟了一个Array的效果,就好像它真的是一个数组一样。

这种方法简单,但是有缺点。因为这种方法是一次性,仅仅在这一次的forEach循环中生效,如果下次再试图对nodeLIst使用另一个Array方法,会因为没有使用强制性方法而导致失败。在下一小节讲述第二种解决方案。

如果对call()方法不太了解,下段文字解释可以快速帮你理解call()。

每个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。例如:

function sum(num1, num2){ 
 return num1 + num2; 
} 
function callSum1(num1, num2){ 
 return sum.apply(this, arguments); // 传入 arguments 对象
} 
function callSum2(num1, num2){ 
 return sum.apply(this, [num1, num2]); // 传入数组
} 
alert(callSum1(10,10)); //20 
alert(callSum2(10,10)); //20 

在上面这个例子中,callSum1()在执行 sum()函数时传入了 this 作为 this 值(因为是在全局作用域中调用的,所以传入的就是 window 对象)和 arguments 对象。而 callSum2 同样也调用了sum()函数,但它传入的则是 this 和一个参数数组。这两个函数都会正常执行并返回正确的结果。

在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是undefined。

call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。

function sum(num1, num2){ 
 return num1 + num2; 
} 
function callSum(num1, num2){ 
 return sum.call(this, num1, num2); 
} 
alert(callSum(10,10)); //20 

在使用 call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用 apply()没有什么不同。至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply()肯定更方便;否则,选择 call()可能更合适。

事实上,传递参数并非 apply()和 call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。下面来看一个例子。

window.color = "red"; 
var o = { color: "blue" }; 
function sayColor(){ 
 alert(this.color); 
} 
sayColor(); //red 
sayColor.call(this); //red 
sayColor.call(window); //red 
sayColor.call(o); //blue 

这个例子是在前面说明 this 对象的示例基础上修改而成的。这一次,sayColor()也是作为全局函数定义的,而且当在全局作用域中调用它时,它确实会显示"red"——因为对 this.color 的求值会转换成对 window.color 的求值。而 sayColor.call(this)和 sayColor.call(window),则是两种显式地在全局作用域中调用函数的方式,结果当然都会显示"red"。但是,当运行 sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的 this 对象指向了 o,于是结果显示的是"blue"。

使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。在前面例子的第一个版本中,我们是先将 sayColor()函数放到了对象 o 中,然后再通过 o 来调用它的;而在这里重写的例子中,就不需要先前那个多余的步骤了。

                                                                                                                       ----节选自《JavaScript高级程序设计第三版》

【 解决方案2 :将NodeList转换成数组,再执行 forEach() 】:

使用Array.prototype.slice(),然后是函数 call() 方法,将NodeList集合转换到一个数组中。

var arr_cells = Array.protptype.slice.call(cells);

arr_cells.forEach(function(cell){
    sum += parseFloat(cell.firstChild.data);
});

 或者,如下是一种较为简单的方法:

var arr_cells = [].slice.call(cells);

arr_cells.forEach(function(cell){
    sum += parseFloat(cell.firstChild.data);
});

【解析】:

slice() 方法返回了对数组的一部分的一个浅拷贝,或者如果没有给定起始值或结束值的话,就是对整个数组的浅拷贝。slice() 方法也是一个函数,这意味着诸如call() 这样的函数式方法可以与其一起使用。在这段代码中,给call() 传入了参数cells,它对参数列表执行必要的转换,把得到的数组传递给slice()。

【深入】

如果要对数组的一部分进行浅拷贝,我们可以在call() 方法中,给定起始值或结束值:

 var arr_cells = [].slice.call(cells, 0,2);

call() 方法的第一个参数值this值,通常是调用对象本身,后面跟着任意数目的参数。在解决方案中,call() 方法会将0, 2这两个参数分别作为slice()函数的起始值和结束值,只会拷贝cells 集合的前2个元素,并转换为数组。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值