JavaScriptのループについて(オブジェクト編)

JavaScriptのオブジェクトや配列のループはどうやるのがベストなんだろうって考えてました。

オブジェクトの場合

おそらくfor...inが一般的かと。

var myObj= {
  hoge: 'HOGE',
  fuga: 'FUGA'
};
for(var key in myObj) {
  console.log(key + ':' + myObj[key]); // プロパティhogeとfugaが出力される
}

でもこれだとプロトタイプチェーンをたどって、組み込みを除く全プロパティを列挙してしまう。

Object.prototype.myFunction = function() {
  console.log('This is myFunction');
};
var myObj= {
  hoge: 'HOGE',
  fuga: 'FUGA'
};
for(var key in myObj) {
  console.log(key + ':' + myObj[key]); // myFunctionまで出力される
}

なので、大抵はこんな感じでhasOwnPropertyのチェックを挟むもんだと思っている。

Object.prototype.myFunction = function() {
  console.log('This is myFunction');
};
var myObj= {
  hoge: 'HOGE',
  fuga: 'FUGA'
};
for(var key in myObj) {
  if(myObj.hasOwnProperty(key)) {
    console.log(key + ':' + myObj[key]); // きっとhogeとfugaだけ
  }
}


あとObject.keysを使ってforで回す方法もあるんですね。
Objectをループしたい時はObject.keys()が便利

Object.prototype.myFunction = function() {
  console.log('This is myFunction');
};
var myObj= {
  hoge: 'HOGE',
  fuga: 'FUGA'
};
// Object.keysでプロトタイプチェーンを遡らずにプロパティの配列を作ることができる
var myObjKeys = Object.keys(myObj);
for(var i=0, len=myObjKeys.length; i<len; i++) {
  console.log(myObjKeys[i] + ':' + myObj[myObjKeys[i]]); // きっとhogeとfugaだけ
}

おー、確かにループが回る度にhasOwnPropertyを叩かなくていいので速そうな感じが。

というわけで試してみた。

// 適当にObjectを拡張
Object.prototype.myFunction1 = function() {
  console.log('This is myFunction1');
};
Object.prototype.myFunction2 = function() {
  console.log('This is myFunction2');
};
Object.prototype.myFunction3 = function() {
  console.log('This is myFunction3');
};
// ある程度大きな数のプロパティ数を持つオブジェクト
var myObj = {};
for(var i=0; i<1000000; i++) {
  myObj[i] = i;
}
var total = 0;
// 計測開始
var start = new Date();
for(var key in myObj) {
  if(myObj.hasOwnProperty(key)) {
    total += myObj[key];
  }
}
console.log(total);
var end = new Date();
// 計測完了して結果出力
console.log(end.getTime() - start.getTime()); // 大体500くらい

for...inでhasOwnPropertyを使いつつ回すときは500ms前後でした。
※node v0.10.26で実行

ならObject.keys使う方はどうか。

// 計測開始
var start = new Date();
var myObjKeys = Object.keys(myObj);
for(var i=0, len=myObjKeys.length; i<len; i++) {
  total += myObj[myObjKeys[i]];
}
console.log(total);
var end = new Date();
// 計測完了して結果出力
console.log(end.getTime() - start.getTime()); // 大体500くらい。ってあれ...

こっちも大体500ms前後でしたよ。
これ結局Object.keysの部分で結構時間食ってるので、ループ処理自体は早いのだけどトータルで見ると変わらないっぽいです。
(計測方法とか前提条件があまりよろしくないのかもしれませんが。。。)


んー今のところ一般的なやり方の選択肢としてはこの2つくらいで、じゃあどっち使うかって話になるんだけど。
実行速度以外で、例えばObject.keysやhasOwnPropertyに対応しているブラウザに差がないかで判断基準を設けたらどうだろうか。

keys:ブラウザのサポート - MDN
hasOwnProperty:解説 - MSDN

はい、どっちもIE9以降でしかサポートされてませんでした。
...状況にもよるけど、これ基本的にどっちでもよさそうですね。

結論

多分、好きな方でいいんじゃないでしょうか。(っておい)
もちろん、状況に応じて使い分けるところは使い分けつつ。


配列も書くつもりだったけど、なんかオブジェクトだけで結構長くなってしまった。
配列は別で書こう。。。