#はじめに
JavaScriptで配列やオブジェクトをループする時どのように書きますか?
シンプルにfor文?
Array.forEachを使う方法もありますね。
あるいはES6から取り入れられたfor-of文でしょうか?
ただこうしたいくつかの方法がある中、僕が以前思ったのは
「で、結局何が良いんだってばよ??( ̄へ ̄|||)」ということです
単なる文法の違いでどれでも良いのか?それともリスクやパフォーマンスがそれぞれ違ったりするだろうか・・?
その疑問の答えを出すために、ループの各手法を主にリスク・パフォーマンスの面で比較・調査して「配列やオブジェクトをループする良い方法」の結論を出してみました。
もし良ければ参考にしてみてください。
#どんなループ方法があるか
まず配列とオブジェクトそれぞれでどんなループ方法があるかを挙げます。
※jQueryやUnderscore.jsでのループ方法はここでは取り上げません。またArray.someやArray.everyなどの関数も用途が限定されてますので除外します。
ここで取り上げるのはあくまで汎用的に使用するループ方法です。
##配列
###for
let ar = [10, 20, 30];
for(let i = 0; i < ar.length; i++) {
console.log(ar[i])
}
###for...in
for(let i in ar) {
console.log(ar[i]);
}
###for...of
for(let v of ar) {
console.log(v);
}
###for...ofとArray.entriesの組み合わせ
for...ofを使う場合に添字と値をいっぺんに取り出したい場合
for(let [i, v] of ar.entries) {
console.log(v);
}
ar.forEach(function(v, i, a){
console.log(v);
});
##オブジェクト
###for...in
let obj = {test1: 10, test2:20, test3:30};
for(let k in obj) {
console.log(obj[k]);
}
###for...ofとObject.keysの組み合わせ
for(let k of Object.keys(obj)) {
console.log(obj[k]);
}
###Array.forEachとObject.keysの組み合わせ
Object.keys(obj).forEach(function (k) {
console.log(obj[k]);
});
#ループ方法の比較
ではこれまでに挙げたループ方法を実際に比較してみます
##リスク
ここで言うリスクとは
- 予期せぬプロパティが参照されてしまわないか
- prototypeが拡張された場合にさかのぼって参照されてしまわないか
を指します。
試しに配列にプロパティを追加・prototype拡張を行った後ループを行ったらどうなるかを検証してみました。
let ar = [10, 20, 30];
// prototype拡張
Array.prototype.extendTest = "prototype拡張";
// Arrayオブジェクトにプロパティ追加
ar.test = "test";
console.log("for");
for(let i = 0; i < ar.length; i++) {
console.log(ar[i]);
}
console.log("-----------------");
console.log("for...in");
for(let i in ar) {
console.log(ar[i]);
}
console.log("-----------------");
console.log("for...of");
for(let v of ar) {
console.log(v);
}
console.log("-----------------");
console.log("for-of entries");
for(let [k, v] of ar.entries()) {
console.log(v);
}
console.log("-----------------");
console.log("Array.forEach");
ar.forEach(function(v){
console.log(v);
});
結果は以下の通り。
for
10
20
30
-----------------
for...in
10
20
30
test
prototype拡張
-----------------
for...of
10
20
30
-----------------
for...of entries
10
20
30
-----------------
Array.forEach
10
20
30
この中でfor...inだけ出力が違うことが分かります。
理由としてfor...inはオブジェクトの"プロパティ"に対してループしているからです. またprototypeをさかのぼって列挙するので拡張されていたら, それもループの対象となってしまいます。
配列ではなくオブジェクトの場合も同様です。
Objectオブジェクトのprototypeが拡張されていれればループの対象になります。
let obj = {test1:10, test2:20, test3:30};
// prototype拡張
Object.prototype.extendFunc = function(){};
console.log("for...in")
for(let k in obj) {
console.log(obj[k]);
}
console.log("-----------------");
console.log("for...of");
for(let k of Object.keys(obj)) {
console.log(obj[k]);
}
console.log("-----------------");
console.log("Array.forEach");
Object.keys(obj).forEach(function (k) {
console.log(obj[k]);
});
for...in
10
20
30
[Function]
-----------------
for...of
10
20
30
-----------------
Array.forEach
10
20
30
従って配列のループにはfor...inを使わない方が良いのと、
オブジェクトのループとして使う場合でも、ループ内でhasOwnPropertyメソッドでそのオブジェクトが持っているプロパティかどうかをチェックする必要があります。
for(let k in obj) {
if(obj.hasOwnProperty(k)) {
console.log(k+ ':' + obj[k]);
}
}
##パフォーマンス
各ループ方法で10000回ループした時と100回ループした時の実行速度を比較します。
[テスト条件]
- 実行環境:Chrome最新版
- 純粋なループ処理の速度を求める為、ループ内では何も処理しない。
- 10回実行して平均実行時間を算出
- 公平を期すため値の評価まで行う
###配列
まずは10000回ループを実行!
// テスト用配列作成
let ar = [];
for(let i = 0; i < 100; i++) {
ar[i] = "test";
}
console.time('for');
for (let i = 0, l = ar.length; i < l; i++) {
ar[i];
}
console.timeEnd('for');
console.time('for...in');
for(let i in ar) {
ar[i];
}
console.timeEnd('for...in');
console.time('for...of');
for(let v of ar) {
v;
}
console.timeEnd('for...of');
console.time('for...of and Array.entries');
for(let pair of ar.entries()) {
pair[1];
}
console.timeEnd('for...of and Array.entries');
console.time('Array.forEach');
ar.forEach(function(v){
v;
});
console.timeEnd('Array.forEach');
for | for...in | for...of | for...of and Array.entries | Array.forEach |
---|---|---|---|---|
0.208ms | 2.242ms | 1.305ms | 2.184ms | 0.522ms |
for...inやfor...of and Array.entriesが一番遅いですね。。
逆に一番速いのがfor文ですね。
ただ10000回ループする処理というのは実際のアプリケーションでは現実的ではありませんので、今度は100回にして同じように実行します。
for | for...in | for...of | for...of and Array.entries | Array.forEach |
---|---|---|---|---|
0.031ms | 0.061ms | 0.074ms | 0.117ms | 0.102ms |
100回だとArray.forEachやfor...of and Array.entriesが遅いです。
ただ実行環境にもよるので一概にこれが確実に遅いということは言えません。
やはり速いのがfor文です。
とはいっても0.0*msの世界なので膨大なループ数でなければどのループ方法を使用してもそこまで速度に大差が無いということが分かりました。
###オブジェクト
配列と同じように比較します。
// テスト用オブジェクトの作成
let obj = {};
for (let i = 0; i < 100; i++) {
obj[`test${i}`] = "test";
}
console.time('for...in');
for (let k in obj) {
obj[k];
}
console.timeEnd('for...in');
console.time('for...of and Object.keys');
for(let k of Object.keys(obj)) {
obj[k];
}
console.timeEnd('for...of and Object.keys');
console.time('Array.forEach and Object.keys');
Object.keys(obj).forEach(function (k) {
obj[k];
});
console.timeEnd('Array.forEach and Object.keys');
[ループ10000回実行速度]
for...in | for...of and Object.keys | Array.forEach and Object.keys |
---|---|---|
5.58ms | 7.803ms | 6.074ms |
[ループ100回実行速度]
for...in | for...of and Object.keys | Array.forEach and Object.keys |
---|---|---|
0.053ms | 0.149ms | 0.13ms |
for...in、Array.forEachとObject.keysの組み合わせ、for...ofとObject.keysの組み合わせの順に速い結果となりました。
特にfor...inが他に比べて一段速いですね。
#その他
今まで挙げた方法の中で、ループの途中で走査を止めたい場合、for・for...in・for...ofはbreak文を使えば出来ます。
しかし、Array.forEachは止められない
break文は使用できないので、もし本当に途中で止めたいのであればtry-catch
文でやる方法もありますが、、おすすめはしません(汗)
#結論
##配列のループ
シンプルにfor文を使いましょう
もしくはfor...ofでも良いです。
逆にfor...in文はリスクの面で使わないようにするのが賢明です。
##オブジェクトのループ
for...ofとObject.keysの組み合わせがベターかと。
for...inは~(以下同文(苦笑))
もしループの途中で止めることが無ければArray.forEachとObject.keysの組み合わせでも良いと思います。
もし他に良い方法のループがあれば是非ご意見を下さい。