JavaScript中的NodeList对象

43

有人能告诉我NodeList是什么类型的对象吗?我读到它是一个类似数组的对象,可以通过方括号表示法访问,例如var a = someNode.childNode[0];。既然我们只能通过方括号表示法来访问对象的属性,而且我们不能有独立的索引属性,那么这怎么可能呢?

8个回答

51

NodeList 是一组 DOM 元素 的集合。它类似于数组(但并非如此)。要使用它,必须将其转换为常规的 JavaScript 数组。下面的代码片段可以为您完成此任务。

const nodeList = document.getElementsByClassName('.yourClass'),
      nodeArray = [].slice.call(nodeList);

更新:

// newer syntax
const nodeList = Array.from(document.querySelectorAll('[selector]'))

// or
const nodeList = [...document.querySelectorAll('[selector]')]

4
基本上,JavaScript将节点列表视为数组,因为您正在使用call方法将空数组的上下文设置为nodeList。然后,只需使用slice方法并且不带参数就可以返回它本身的精确副本。希望这回答了您的问题(: - brielov
@brielov 这并不完全正确,类似 [].forEach.call 的范式批判在这里 - https://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/ - 提供了更好的上下文。为了展示它并不像解释的那样工作,尝试运行 [1, 2, 3].slice.call(nodeList) 并注意你会得到相同的结果。尝试玩弄原始数组中的条目数量,你仍然会得到相同的结果。 - romellem
你是对的,HTMLCollection 只能包含元素,而 NodeList 可以包含任何类型的节点,比如文本节点和注释节点,如果我没记错的话。不过例子很清楚,它展示了如何将任何 DOM 查询响应转换为原始数组。 - brielov
@romellem 我已经阅读了那篇文章,它有一些有效的观点。我想这取决于需求。例如,我自己从来没有需要支持IE,而且我倾向于功能式编程,对我来说,for循环比forEach递归略微“不纯”,Array.from(nodeList).map(n => n.textContent) 这个一行代码就能搞定的例子非常优美。 - brielov
如果你真的需要将NodeList转换为数组,那么Array.from(nodeList)[...nodeList]都是很好的工具。然而,如果你发现自己写了[...nodeList].forEach(),那么你可能只需遍历普通的NodeList,而不是将其展开成一个数组。如果你担心浏览器支持问题,有一个非常简单的Polyfill - if (window.NodeList && !NodeList.prototype.forEach) NodeList.prototype.forEach = Array.prototype.forEach - romellem
显示剩余3条评论

39

NodeList 是一个宿主对象,不受适用于本地 JavaScript 对象的通常规则的约束。因此,您应该遵循其文档化的 API,该 API 由 length 属性和通过方括号属性访问语法访问其成员的方式组成。您可以使用此 API 创建一个包含 NodeList 成员快照的 Array

var nodeList = document.getElementsByTagName("div");
var nodeArray = [];
for (var i = 0; i < nodeList.length; ++i) {
    nodeArray[i] = nodeList[i];
}

2
这是否意味着 nodeList 不能被 for-in 循环遍历? - Tristian
1
@Triztian:确实如此。至少不能保证它能正常工作,我认为在IE中也不行。 - Tim Down
1
在Google Chrome中也不起作用,您必须使用length属性并访问元素,就像它是一个数组一样。 - Tristian
1
啊哈,这就解释了为什么 forEach 可以用于类型为 Array 的对象,但是不能用于 bilbo.getElementsByTagName("taggins") - Ian Campbell

10

NodeList不是JavaScript的核心对象,它是由浏览器提供的DOM。可以将其视为返回动态或实时对象接口的函数,因此forEach()方法不可用,但可以将其转换为真实数组以获得快照,例如:

// turns nodelist into an array to enable forEach
function toArray(list) {
  var i, array = [];
  for  (i=0; i<list.length;i++) {array[i] = list[i];}
  return array;
}

细节:http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-536297177


我想它是用像C++这样的语言实现的。即便如此,它们如何与JavaScript代码通信呢? - ppoliani
这取决于浏览器和JavaScript引擎。你为什么想知道这个? - Martijn
我想弄清楚DOM API是否是用EcmaScript编写的,但据我所知,它不是。 - ppoliani
所以,你的问题是关于实现者资源的?针对Firefox,请访问以下链接:http://mxr.mozilla.org/mozilla-central/ident?i=nsIDOMNodeList - Torsten Becker

9
是“动态”的,也就是说当文档结构发生变化时会更新,并始终保持最准确的信息。实际上,所有对象都是针对DOM运行的查询,只要访问它们,就会更新。

无论何时您想迭代,最好初始化第二个变量并将迭代器与该变量进行比较:

var divs = document.getElementsByTagName("div");

for (var i=0, lens=divs.length; i  <  len; i++){
    var div = document.createElement("div");
    document.body.appendChild(div);
} 

NodeList是类似于数组的结构,但它实际上并不是一个数组。您可以通过方括号表示法访问数组值。


你刚才回答的内容写在我正在阅读的书里。我不知道array-like是什么意思。EcmaScript没有指定任何类似的东西。我猜这是一种实现技术。 - ppoliani
1
它不在ECMA的DOM规范中。你可以像一个数组一样迭代它(使用方括号表示法),但你不能使用Array方法如push(),splice()或reverse()来操作它。 - ipopa
也就是说,它运行在浏览器级别的代码中(例如C++)? - ppoliani
浏览器实现了它,所以很可能是可以的。 - ipopa

9

JavaScript就像酒精一样,它可以强制执行:

var links = document.getElementsByTagName('a');
Array.prototype.slice.call(links).forEach(function(anchor, index, arr) {
  anchor.addEventListener('click', onClickFN, false);
});

或者只需使用 Array.prototype.forEach.call(links, function() {}) ;) - yckart
或者只是 [].slice.call - Mahi

7

对我来说非常好用。Chrome,2016年10月。 - Tim Erickson

7
节点列表通常被实现为带有过滤器的节点迭代器。这意味着获取像长度这样的属性的时间复杂度是 O(n),通过重新检查长度来遍历列表将会是 O(n^2)。
var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}

最好这样做:

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
   doSomething(paragraph);
} 

这对于所有集合和数组都适用,只要数组不包含被视为布尔假的内容。 在遍历 childNodes 的情况下,您还可以使用 firstChild 和 nextSibling 属性。
var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}

1
嗯,不仅仅是 boolean false 值会打破循环,所有 falsey (undefined, null, 0 等) 值都会... - yckart

2

概述:

NodeList对象是表示节点集合的数据结构。在DOM上下文中,节点可以是以下内容:

  1. 文档本身
  2. DOM元素(即HTML / SVG元素)
  3. 文本
  4. 注释

NodeList不是数组,但NodeList是可迭代的数据结构,这意味着我们可以使用for..of循环遍历其值(即节点项)。此外,在NodeList的原型上有一些不错的实用函数,使得与它们一起工作更加方便。

示例:

const parent = document.querySelector('.parent');
const myNodeList1 = parent.childNodes; // this property is a Nodelist
console.log(myNodeList1 instanceof NodeList);

const myNodeList2 = document.querySelectorAll('.foo'); // this method returns a Nodelist
console.log(myNodeList2 instanceof NodeList);

// looping over the items of a nodelist
for (let el of myNodeList2) {
  el.innerHTML = 'hi';
}

// getting the length of a nodeList 
console.log(myNodeList2.length);
<div class="parent">
  <div class="foo"></div>
  <div class="foo"></div>
</div>

以下是浏览器(chrome)devtools中Nodelist的外观:

enter image description here


可以使用以下符号访问NodeList的元素:
 myNodelist[0]; // grabs the first item of the NodeList

因为你只是一个对象中使用键的属性值。在这个例子中,属性的键是数字零,而值则是DOM元素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接