JavaScript中合并两个对象(NodeList)数组

16

我试图合并两个对象数组,以便可以验证表单。通常的concat方法在这种情况下似乎不起作用。Concat适用于普通的数字和字符串数组,但不适用于对象数组。代码行 var allTags = allInputs.concat(allSelects); 无法运行。

var allInputs = document.getElementsByTagName("input");
alert("Inputs: " + allInputs.length);

var allSelects = document.getElementsByTagName("select");
alert("Selects: " + allSelects.length);

var allTags = allInputs.concat(allSelects);
alert("allTags: " + allTags.length);

1
这不是数组,而是类似数组的对象(NodeList)。 - salexch
实际上,它是一个NodeList,因此没有concat方法。 - THEtheChad
5个回答

26

Concat可以与普通数字和字符串数组一起使用,但无法与对象数组一起使用。

实际上,它可以,但是NodeList实例没有concat方法,Array#concat没有一种识别您想要展开它们的方式(因为它们不是数组)。

但这仍然相当容易做到(尽管请注意下面的警告)。更改此行:

var allTags = allInputs.concat(allSelects);

to

var allTags = [];
allTags.push.apply(allTags, allInputs);
allTags.push.apply(allTags, allSelects);

演示 | 源代码

这个技巧的原理是: Array#push 接受变量数量的元素添加到数组中,Function#apply 调用函数时使用给定的值作为 this(在我们的例子中是 allTags)以及任何类似数组的对象作为要传递给它的参数。由于 NodeList 实例是类似数组的对象,因此 push 很高兴地将列表的所有元素推入数组中。

Function#apply 的这种行为(不需要第二个参数真正是一个数组)在规范中被非常清楚地定义,并且在现代浏览器中得到了很好的支持。

遗憾的是,IE6和7不支持上述特性(我认为它特别是在使用宿主对象 NodeLists 作为 Function#apply 的第二个参数时),但是,我们也不应该支持 它们。:-) IE8也不支持,这更有问题。IE9是可以的。

如果您需要支持 IE8 及更早版本,那么遗憾的是,我认为你只能使用一个老套的循环:

var allInputs = document.getElementsByTagName('input');
var allSelects = document.getElementsByTagName('select');
var allTags = [];

appendAll(allTags, allInputs);
appendAll(allTags, allSelects);

function appendAll(dest, src) {
  var n;

  for (n = 0; n < src.length; ++n) {
    dest.push(src[n]);
  }

  return dest;
}

实时示例 | 源代码

这个确实可以在IE8及更早版本(以及其他浏览器)上运行。


@user2035797:不用担心,我还添加了一个在IE8及更早版本上工作的示例。 - T.J. Crowder

2

document.get[something] 返回一个 NodeList,它看起来非常类似于一个 Array,但有很多独特的特点,其中两个是:

  • 它不包含 concat 方法
  • 项目列表是 实时的。这意味着它们会随着文档在实时更改。

如果您不介意将您的 NodeList 转换为实际的数组,则可以执行以下操作以实现所需的效果:

var allInputs  = document.getElementsByTagName("input")
  , allSelects = document.getElementsByTagName("select")
;//nodeLists

var inputList  = makeArray(allInputs)
  , selectList = makeArray(allSelects)
;//nodeArrays

var combined   = inputList.concat(selectList);

function makeArray(list){
  return Array.prototype.slice.call(list);
}

您将失去原始NodeList的所有行为,包括其实时更新以反映DOM更改的能力,但我猜这并不是真正的问题。


请注意,就像我在答案的第一部分中提供的类似解决方案一样,这种方法在IE8或更早版本中不起作用。(在此处尝试:http://jsbin.com/iledok/1)*叹气* 至少IE9还可以。因此,在IE8及更早版本中,需要使用一个无聊的老循环。 - T.J. Crowder

1
你所处理的是HTMLCollection对象,它们是实时集合(即,当你将节点添加到DOM中时,它会自动添加到集合中)。
不幸的是,它没有concat方法……有一个原因。你需要将其转换为数组,从而失去其实时行为:
var allInputsArray = [].slice.call(allInputs),
    allSelectsArray = [].slice.call(allSelects),
    allFields = allInputsArray.concat(allSelectsArray);

1
我找到了一种简单的方法来按照页面或表单上出现的顺序收集dom对象。 首先,在对象上创建一个虚拟的类样式'reqd',然后使用以下代码,它将按照它们在页面上出现的顺序创建一个列表。只需将该类添加到您希望收集的任何对象中即可。
var allTags = document.querySelectorAll("input.reqd, select.reqd");

0

你有没有研究过underscore.js?你可以这样做:

 var allSelectsAndInputs = _.union(_.values(document.getElementsByTagName("input")),_.values(document.getElementsByTagName("select")));

更多信息请参见:http://underscorejs.org/#extend

该函数旨在用于对象,但在此设置中也可以使用。


实际上,我正在尝试它...它似乎有点不对劲。请稍等。 - Zeke Nierenberg
如果您喜欢,请考虑为我的答案投票。 - Zeke Nierenberg

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