如何将DOM节点列表转换为Javascript中的数组?


96

我有一个接受HTML节点列表的Javascript函数,但它需要一个Javascript数组(它在该数组上运行一些Array方法),并且我想向其提供Document.getElementsByTagName返回DOM节点列表的输出。

最初,我想到了使用简单的方法:

Array.prototype.slice.call(list,0)

而且,这在所有浏览器中都可以正常运行,除了Internet Explorer会返回错误“ JScript对象预期”,因为Document.getElement*方法返回的DOM节点列表显然不足以成为函数调用目标的JScript对象。

注意事项:我不介意编写Internet Explorer特定的代码,但是我不允许使用任何Javascript库(例如JQuery),因为我正在编写要嵌入到第三方网站中的小部件,并且无法加载外部库会给客户带来冲突。

我最后的努力是遍历DOM节点列表并自己创建一个数组,但是有更好的方法吗?


更好的是,创建一个要从DOM节点列表转换的函数,但这确实是我的解决方案,我认为您做对了。
克里斯托弗·萨尔·​​斯托加德

> for(i = 0; i <x.length; i ++)为什么每次迭代都获得NodeList的长度?这不仅浪费时间,而且由于NodeLists是实时集合,如果循环主体中的任何内容改变了它的长度,您可能会无休止地循环或使索引越界。如果将长度分配给变量,则后者是最糟糕的情况,错误比无限循环好得多。

这是一个非常老的问题,但是jQuery是使用.noConflict方法专门构建的,因此它不会与其他库(甚至是自身)产生冲突,这意味着可以在页面上加载多个版本的jQuery。也就是说,除非绝对必要,否则最好避免使用/加载库。
vol7ron

@ vol7ron:快进到2016年,每个人仍然对javascript库添加到页面的大小持保守态度。当然,JQuery缩小并压缩了30KB,仅转换一个节点列表,它还有30KB太多了:-)
Guss

Answers:


64

ECMAScript规范指出,NodeList是主机对象Array.prototype.slice不能保证在主机对象上使用方法可以正常工作。

slice函数是否可以成功应用于主机对象取决于实现。

我建议您做一个简单的函数来遍历NodeList并将每个现有元素添加到数组中:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

更新:

正如其他答案所建议的那样,您现在可以在现代环境中使用传播语法或Array.from方法:

const array = [ ...nodeList ] // or Array.from(nodeList)

但是考虑一下,我想将NodeList转换为Array的最常见用例是对其进行迭代,现在该NodeList.prototype对象具有本forEach机方法,因此,如果您在现代环境中,则可以直接使用它,也可以使用一个粉尘。


2
这是在创建一个数组,其原始顺序颠倒了,我不认为这是OP想要的。您是array[i] = obj[i]不是要这样做array.push(obj[i])
Tim Down'4

@Tim,对,我以前有这样的感觉,但是昨天晚上编辑时却没有注意到(当地时间凌晨3点:),谢谢!
CMS 2010年

9
在什么情况下,obj.length除了整数值以外,还有什么其他值?
彼得

1
我简直不敢这么复杂。丑陋。这是Web / JS编程中非常普遍的需求。下一版语言的新方法?
安德鲁·科珀

1
@AlbertoPerez,不客气!Saludos hasta马德里!
CMS

126

es6中,您可以按以下方式使用:

  • 点差运算符

     var elements = [... nodelist]
  • 使用 Array.from

     var elements = Array.from(nodelist)

有关更多参考,参见https://developer.mozilla.org/zh-CN/docs/Web/API/NodeList


4
Array.from():D
Josan Iracheta

4
如果有人将这种方法与Typescript一起使用(至ES5),则只能Array.from使用,因为TS将其转换为nodelist.slice-不支持。
彼得·艾伯特

在您和您通过我的选票之前,我在同一年回答过?我无法解释这个..
VSYNC

3
@vsync,您的回答没有提及Array.from
ESR

@EdmundReed-是吗?如何证明这一点。它的编写时间更长,因此在实际情况下,它将永远不会被使用,只会spread被使用。
vsync

16

使用点差(ES2015),就像:[...document.querySelectorAll('p')]

(可选:使用Babel将上述ES6代码转换为ES5语法)


在浏览器的控制台中尝试一下,看看魔术:

for( links of [...document.links] )
  console.log(links);

至少在最新的Chrome(44)上,我得到了:未捕获的TypeError:document.querySelectorAll不是一个函数(…)
Nick

@OmidHezaveh-正如我所说的,这是ES6代码。我不知道Chrome 44是否支持ES6,如果支持,覆盖范围如何。它已经使用了将近一年,而您显然必须在支持ES6扩展的浏览器上运行此代码。
vsync

或在执行之前将其翻译为es5
HelloWorld

8

使用这个简单的把戏

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})

您能否解释一下为什么您认为这比使用Array.prototype.slice(或[].slice如您所说的)具有更好的成功机会?值得注意的是,我想评论一下我在Q中记录的IE特定错误发生在IE 8或更低版本中,map无论如何都没有实现。在IE 9(“标准模式”)或更高,两者slicemap以同样的方式成功。
2015年

6

尽管它并不是真正的垫片,但由于没有规范要求使用DOM元素,因此我专门为您提供了一种使用slice()方式:https : //gist.github.com/brettz9/6093105

更新:当我使用DOM4规范的编辑器提出此问题时(询问它们是否可能对宿主对象添加自己的限制(以便该规范在与数组方法一起使用时要求实现者正确地转换这些对象))超出了ECMAScript规范,允许实现独立性),他回答说:“根据ES6 / IDL,主机对象已过时。” 我在http://www.w3.org/TR/WebIDL/#es-array上看到规范可以使用此IDL定义“平台数组对象”,但http://www.w3.org/TR/domcore/却没有似乎并没有为此使用新的IDL HTMLCollection(尽管它似乎只是这样做了,Element.attributes尽管它只是明确地声明将WebIDL用于DOMString和DOMTimeStamp)。我看到了[ArrayClass](继承自Array.prototype)用于NodeListNamedNodeMap现在不推荐使用,而只支持仍在使用它的唯一项Element.attributes)。无论如何,看起来它已经成为标准。Array.from对于此类转换,ES6 也可能比必须指定Array.prototype.slice且在语义上比[].slice()Array.slice()据我所知,较短的形式(“数组通用”)尚未成为标准行为)更方便。


我已进行了更新,以指示规范可能正在朝着要求这种行为的方向发展。
Brett Zamir

5

今天,在2018年,我们可以使用ECMAScript 2015(第6版)或ES6,但并非所有浏览器都可以理解(例如,IE不能完全理解)。如果你愿意,你可以使用ES6如下: var array = [... NodeList];为传播运营商)或var array = Array.from(NodeList);

在其他情况下(如果您不能使用ES6),可以使用最短的方法将a转换NodeListArray

var array = [].slice.call(NodeList, 0);

例如:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

但是,如果您只想DOM轻松遍历节点列表,则无需将a转换NodeListArray。可以循环NodeList使用的项目:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

不要试图使用for...infor each...in枚举列表中的项目,因为这也会枚举的长度和项目属性,NodeList如果脚本假定它只需要处理元素对象,则会导致错误。另外,for..in不能保证以任何特定顺序访问属性。for...of循环将正确循环遍历NodeList对象。

另请参阅:


3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

这应该可以工作,跨浏览器,并获得所有“元素”节点。


1
这与@CMS的答案基本相同,不同之处在于它假定我只想要元素节点,而我不需要。
格斯2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.