将 HTMLCollection 转换为数组的最有效方法
Posted
技术标签:
【中文标题】将 HTMLCollection 转换为数组的最有效方法【英文标题】:Most efficient way to convert an HTMLCollection to an Array 【发布时间】:2010-09-18 09:08:17 【问题描述】:除了遍历所述集合的内容并手动将每个项目推入数组之外,是否有更有效的方法将 htmlCollection 转换为数组?
【问题讨论】:
什么是“高效”?如果性能最好,for 循环通常比 Array.prototype.slice 更快。循环也适用于更广泛的浏览器(即所有浏览器),因此根据这些标准,它是“最有效的方式”。而且它的代码很少:for (var a=[], i=collection.length; i;) a[--i] = collection[i];
所以那里没有太多的“骗局”:-)
@RobG 谢谢 - 如果可以的话,我会给你 +59k! ;-)
看看current browser performance,slice 在性能方面几乎赶上了循环,除了 Chrome。使用更多的元素和对循环的轻微优化,results are almost identical,除了在 Chrome 中循环要快得多。
我创建了一个 jsperf 测试,它查看@harpo 提到的两种方法以及一个 jquery 性能测试。我发现 jquery 比 javascript 方法稍慢,并且 js 测试用例之间的***性能有所不同。 Chrome 59.0.3071 / Mac OS X 10.12.5 更喜欢使用Array.prototype.slice.call
,而 Brave(基于 Chrome 59.0.3071)在多次运行的两个 javascript 测试之间几乎没有区别。见jsperf.com/htmlcollection-array-vs-jquery-children
jsben.ch/h2IFA => 最常用方法的性能测试
【参考方案1】:
有时,即使您以正确的方式编写代码,但仍然无法正常工作。
var allbuttons = document.getElementsByTagName("button");
console.log(allbuttons);
var copyAllButtons = [];
for (let i = 0; i < allbuttons.length; i++)
copyAllButtons.push(allbuttons[i]);
console.log(copyAllButtons);
你得到空数组。 喜欢,这个
HTMLCollection []
[]
Console_javascript
为了解决这个问题,你必须在html文件的body标签之后添加javascript文件的链接。
<script src="./script.js"></script>
如下所示, html_file
最终输出
HTMLCollection(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b, b: button#b]
(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b]
【讨论】:
【参考方案2】:我认为在HTMLCollection
的实例上调用Array.prototype
函数 比将集合转换为数组(例如[...collection]
或Array.from(collection)
)要好得多,因为在后者如果一个集合被不必要的隐式迭代并创建了一个新的数组对象,这会占用额外的资源。在具有从[0]
开始的连续数字键和具有此类键数量的有效数值的length
属性的对象上可以安全地调用Array.prototype
迭代函数(包括例如HTMLCollection
和FileList
的实例),所以这是一种可靠的方法。另外,如果经常需要这样的操作,可以使用空数组[]
快速访问Array.prototype
函数;或者可以创建Array.prototype
的快捷方式。一个可运行的例子:
const _ = Array.prototype;
const collection = document.getElementById('ol').children;
alert(_.reduce.call(collection, (acc, textContent , i) =>
return acc += `$i+1) $textContent` + '\n';
, ''));
<ol id="ol">
<li>foo</li>
<li>bar</li>
<li>bat</li>
<li>baz</li>
</ol>
【讨论】:
【参考方案3】:var arr = Array.prototype.slice.call( htmlCollection )
使用“本机”代码将具有相同的效果。
编辑
由于这获得了很多意见,请注意(根据@oriol 的评论)以下更简洁的表达式实际上等效:
var arr = [].slice.call(htmlCollection);
但请注意,根据@JussiR 的评论,与“详细”形式不同,它确实在该过程中创建了一个空的、未使用的且确实不可用的数组实例。编译器对此做了什么超出了程序员的理解。
编辑
从 ECMAScript 2015 (ES 6) 开始,还有 Array.from:
var arr = Array.from(htmlCollection);
编辑
ECMAScript 2015 还提供了spread operator,它在功能上等同于Array.from
(尽管请注意Array.from
支持将映射函数作为第二个参数)。
var arr = [...htmlCollection];
我已确认上述两种方法都适用于 NodeList
。
上述方法的性能比较:http://jsben.ch/h2IFA
【讨论】:
快捷方式[].slice.call(htmlCollection)
也可以使用。
@ChrisNielsen 是的,我被误导了。很抱歉传播了这个。我没有意识到我在这里也说过。删除了评论以避免混淆,但对于上下文,我在某处阅读(或误读)切片 HTMLCollection 使其表现得既像数组又像集合。完全错误。
[].slice 快捷方式不等效,因为它还会创建未使用的空数组实例。不过,不确定编译器是否能够将其优化掉。
Array.from
,即from
,IE11不支持。
Typescript 不允许传播运算符,因为 htmlCollection 没有 [Symbol.iterator]()
方法。【参考方案4】:
要以有效的方式将类数组转换为数组,我们可以使用 jQuery makeArray
:
makeArray:将类数组对象转换为真正的 JavaScript 数组。
用法:
var domArray = jQuery.makeArray(htmlCollection);
一点额外的:
如果你不想保持对数组对象的引用(大多数时候 HTMLCollections 是动态变化的,所以最好将它们复制到另一个数组中,这个例子密切关注性能:
var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.
for (var i = 0 ; i < domDataLength ; i++)
resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
什么是类数组?
HTMLCollection 是一个"array-like"
对象,array-like 对象类似于数组的对象,但缺少很多功能定义:
类数组对象看起来像数组。他们有各种编号 元素和长度属性。但这就是相似之处停止的地方。 类数组对象没有 Array 的任何函数,而 for-in 循环甚至不起作用!
【讨论】:
【参考方案5】:这适用于所有浏览器,包括早期的 IE 版本。
var arr = [];
[].push.apply(arr, htmlCollection);
由于目前jsperf还在down,这里有一个jsfiddle,比较不同方法的性能。 https://jsfiddle.net/qw9qf48j/
【讨论】:
试试var args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
【参考方案6】:
不确定这是否是最有效的,但简洁的 ES6 语法可能是:
let arry = [...htmlCollection]
编辑:另一个,来自 Chris_F 评论:
let arry = Array.from(htmlCollection)
【讨论】:
另外,ES6 增加了Array.from()
注意第一个,当使用 babel 进行编译时存在一个微妙的错误,其中 [...htmlCollection] 将返回一个带有 htmlCollection 的数组,因为它是唯一的元素。
数组扩展运算符不适用于 htmlCollection。仅适用于 NodeList。
Array.from
,即from
,IE11不支持。
Benchmark 看起来扩展运算符在这两个中更快。【参考方案7】:
我看到了一个更简洁的方法来获取Array.prototype
方法,通常也可以。将HTMLCollection
对象转换为Array
对象如下所示:
而且,如 cmets 中所述,对于 IE7 及更早版本的旧浏览器,您只需使用兼容性功能,例如:
function toArray(x)
for(var i = 0, a = []; i < x.length; i++)
a.push(x[i]);
return a
我知道这是一个老问题,但我觉得接受的答案有点不完整;所以我想我会把它扔在那里 FWIW。
【讨论】:
【参考方案8】:这是我个人的解决方案,基于此处的信息(此线程):
var Divs = new Array();
var Elemns = document.getElementsByClassName("divisao");
try
Divs = Elemns.prototype.slice.call(Elemns);
catch(e)
Divs = $A(Elemns);
Gareth Davis 在他的帖子中描述了 $A:
function $A(iterable)
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
如果浏览器支持最好的方式,ok,否则使用跨浏览器。
【讨论】:
一般来说,我不希望 try/catch 成为管理控制流的有效方法。您可以先检查该功能是否存在,然后运行其中一个或另一个便宜一点。 与 Gareth Davis 的回答一样,这会在稀疏数组中创建新的未定义成员,因此[,,]
变为 [undefined, undefined]
。
我还没有遇到这种麻烦。它将 3 个元素的集合拼接成一个包含 2 个元素的数组。至于empty变得未定义,这是JavaScript的一些限制,我猜你期待的是null而不是undefined,对吧?【参考方案9】:
对于跨浏览器实现,我建议您查看 prototype.js $A
函数
copyed from 1.6.1:
function $A(iterable)
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
它不使用Array.prototype.slice
可能是因为它并非在每个浏览器上都可用。恐怕性能很差,因为回退是 iterable
上的 javascript 循环。
【讨论】:
OP 要求另一种方法,而不是“遍历所述集合的内容并手动将每个项目推入数组”,但这正是$A
函数大部分时间所做的。
我想我想说的是没有一个很好的方法来做到这一点,prototype.js 代码显示你可以寻找一个'toArray'方法但迭代失败最安全的路线
这将在稀疏数组中创建新的未定义成员。在分配之前应该有一个 hasOwnProperty 测试。以上是关于将 HTMLCollection 转换为数组的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章
如何在不清空 HTMLCollection 的情况下将其转换为数组?