JavaScript NodeListを配列に変換する最も速い方法は?


251

以前にここで回答された質問は、これが最も速い方法であると述べました:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

私のブラウザーでのベンチマークでは、これよりも3倍以上遅いことがわかりました。

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

どちらも同じ出力を生成しますが、特にここで他の人が別の言い方をしているので、私の2番目のバージョンが最速の方法であるとは信じられません。

これは私のブラウザー(Chromium 6)の癖ですか?またはより速い方法はありますか?

編集:気にかける人のために、私は以下に落ち着きました(これは私がテストしたすべてのブラウザで最も速いようです):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2:私はさらに速い方法を見つけました

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));

2
arr[arr.length] = nl[i];arr.push(nl[i]);関数呼び出しを回避するため、よりも高速になる場合があります。
Luc125

9
このjsPerfページは、このページのすべての回答を追跡しています。jsperf.com
to

「EDIT2:私はより速い方法を見つけました」がIE8では92%遅いことに注意してください。
カミロマーティン

2
あなたはすでにあなたが持っているノードの数を知っているので:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems

@ Luc125ブラウザーによって異なりますが、pushの実装は最適化されている可能性があるため、v8はこのようなものに適しているため、Chromeについて考えています。
axelduch

回答:


197

2つ目は一部のブラウザーで高速になる傾向がありますが、最初の1つは単にクロスブラウザーではないため、それを使用する必要があるということが重要です。時代は変わらないが

@kangax IE 9プレビュー

Array.prototype.sliceは、特定のホストオブジェクト(NodeListなど)を配列に変換できるようになりました。これは、最近のブラウザの大部分がかなり長い間実行できたものです。

例:

Array.prototype.slice.call(document.childNodes);

??? どちらもクロスブラウザ互換です-Javascript(少なくともECMAscript仕様と互換性があると主張している場合)はJavascriptです。配列、プロトタイプ、スライス、および呼び出しはすべて、コア言語+オブジェクト型の機能です。
Jason S

6
しかし、IEのNodeListで使用することはできません(これは問題ですが、私の更新を参照してください)
gblazex 2010

9
NodeListは言語の一部ではないため、特にIEでバグがある/予測できないことが知られているDOM APIの一部です
gblazex

3
IE8を考慮に入れると、Array.prototype.sliceはクロスブラウザーではありません。
Lajos Meszaros、2015

1
はい、それが私の答えの基本的なものでした:)今日(2015)よりも2010年の方が関連性が高かったのですが。
gblazex 2015

224

ES6では、NodeListから配列を作成する簡単な方法として、Array.from()関数があります。

// nl is a NodeList
let myArray = Array.from(nl)

この新しいES6の方法は、上で述べた他の方法とどのように速度が比較されますか?
user5508297 2016

10
@ user5508297スライスコールトリックよりも優れています。forループよりも遅くなりますが、それだけではなく、走査せずに配列を作成したい場合もあります。そして構文は美しく、シンプルで覚えやすいです!
webdif

:Array.fromのいいところは、マップ引数に使用できるということですconsole.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajdeを


19

いくつかの最適化:

  • NodeListの長さを変数に保存する
  • 設定する前に、新しい配列の長さを明示的に設定します。
  • プッシュしたりシフトしたりするのではなく、インデックスにアクセスします。

コード(jsPerf):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}

配列を作成して個別にサイズを変更するのではなく、Array(length)を使用することで、もう少し時間を節約できます。次に、constを使用して配列とその長さを宣言し、ループ内でletを使用すると、上記の方法よりも約1.5%速くなります。const a = Array(nl.length)、c = a.length; for(let b = 0; b <c; b ++){a [b] = nl [b]; jsperf.com/nodelist-to-array/93を
ブライアンフロイト

15

結果は完全にブラウザに依存し、客観的な判断を下すために、いくつかのパフォーマンステストを行う必要があります。いくつかの結果を示します。ここで実行できます

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

IE9プラットフォームプレビュー3:


1
forループの逆がこれらにどのように対応するのでしょうか... for (var i=o.length; i--;)...これらのテストの「forループ」は、すべての反復で長さプロパティを再評価しましたか?
Dagg Nabbit、2010


9

ES6では、次のいずれかを使用できます。

  • Array.from

    let array = Array.from(nodelist)

  • スプレッドオペレーター

    let array = [...nodelist]


以前の回答ですでに記述されているものを新しく追加しません。
ステファンベッカー

7
NodeList.prototype.forEach = Array.prototype.forEach;

これで、document.querySelectorAll( 'div')。forEach(function()...)を実行できます


@John、ありがとうございます。しかし、NodeList動作していないが、Object次のとおりです。 Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
イアン・キャンベル

3
Object.prototypeは使用しないでください。JQueryを破壊し、辞書リテラル構文など多くのものを破壊します。
Nate Symer、2015

もちろん、ネイティブの組み込み関数の拡張は避けてください。
ローランド

5

より速くそしてより短く:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
なぜ>>>0?そして、なぜforループの最初の部分に割り当てを配置しないのですか?
Camilo Martin

5
また、これにはバグがあります。場合l0、ループは、従って、終了する0(思い出してくれるインデックスの要素があります番目の要素がコピーされません0
カミロマーティン

1
この答えが大好きですが...疑問に思う人は、ここでは必要ない>>> かもしれませんが、ノードリストの長さが配列仕様に準拠していることを保証するために使用されます。符号なし32ビット整数であることを保証します。ecma-international.org/ecma-262/5.1/#sec-15.4をチェックしてください。判読できないコードが必要な場合は、@ CamiloMartinの提案に従ってこのメソッドを使用してください。
Todd

@CamiloMartinへの返信-置くのは危険です varfor「可変巻き上げ」のため、ループの最初の部分です。の宣言はvarvar行がどこか下に表示されている場合でも、関数の上部に「巻き上げ」られます。これにより、コードシーケンスからは明らかでない副作用が発生する可能性があります。たとえば、forループのに発生する同じ関数内の一部のコードは、宣言されていないことに依存alいる場合があります。したがって、より高い予測可能性を得るには、関数の先頭で変数を宣言します(またはES6の場合はconstletかわりに、ホイストしないものを使用します)。
brennanyoung

3

同じことについて語っているこちらのブログ投稿をチェックしてください。私が集めたものから、余計な時間はスコープチェーンを上っていく必要があるかもしれません。


面白い。私は今いくつかの同様のテストを行ったところ、Firefox 3.6.3はどちらの方法でも速度の増加を示していませんが、Opera 10.6は20%増加し、Chrome 6は手動の反復プッシュ方法で230%(!)増加しています。
jairajs89

@ jairajs89はかなり奇妙です。と思われるArray.prototype.slice、ブラウザに依存します。それぞれのブラウザがどのアルゴリズムを使っているのかしら。
Vivin Paliath


1

この投稿の日付で更新されたチャートは次のとおりです(「不明なプラットフォーム」チャートはInternet Explorer 11.15.16299.0です):

Safari 11.1.2 Firefox 61.0 クローム68.0.3440.75 Internet Explorer 11.15.16299.0

これらの結果から、preallocate 1メソッドが最も安全なクロスブラウザベットであると思われます。


1

と仮定するとnodeList = document.querySelectorAll("div")、これはnodelist配列に変換する簡潔な形式です。

var nodeArray = [].slice.call(nodeList);

私がここでそれを使うのを見てください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.