解決編: JavaScriptで配列やオブジェクトのキーを反復するイディオム
先日の続きです。
escape_artistさんから詳細なコメントを頂きました。ありがとうございます。
また、身内に「ネイティブのforを使ってはいけない」の真意を聞いてみました。
その結果、疑問が解決したのでメモしておきます。
疑問1: for inで配列をループしてはいけないの?
使わないほうがよいそうです。理由は次のとおりです。
- Arrayのprototypeが拡張されているリスクがあるから
- 配列要素以外のプロパティが追加されているリスクがあるから
- ふつうのforの方がパフォーマンスがよいから
理由1: Arrayのprototypeが拡張されているリスクがあるから
escape_artistさんに頂いたコメントが分かりやすいので、そのまま引用させていただきます。
inは仰るとおりプロトタイプを辿るため、Array.prototypeに何かメソッドが追加されているとそれも列挙されてしまいます。
Array.prototype.alertLength = function(){ alert(this.length); };
↑こういう変な拡張をしているライブラリを使っていれば、すべての配列のfor inに"alertLength"が出現するようになってしまう。もちろんArray.prototypeを無計画に拡張するのは行儀が悪いのですが、、このリスクに備えるという意味です。
たしかに、これは危なそうです。
理由2: 配列要素以外のプロパティが追加されているリスクがあるから
理由1と似ています。
配列はオブジェクトなので、要素以外のプロパティを追加できてしまいます。
var ar = [1, 2, 3]; ar.pet = "cat";
こんなところに、謎のネコが。。。
なお、このときの配列のlengthは、要素のインデックス上限 + 1 の値になります。
console.log(ar.length);
3
隠し持っているプロパティ「pet」はノーカウントです。まずい、ネコに気づかないかも。
しかし、for inを使うと、(存在する要素数 + 存在するプロパティ数)回、ループが実行されます。
for (var n in ar) { console.log(n); }
0 1 2 pet
(ネコきたー)
配列をfor inで回す時、for inの中に書くのは、配列の要素に対して実行したい処理だと思います。しかし、配列オブジェクトがプロパティを持っていると、プロパティに対しても同様の処理が適用されます。はたしてそれは意図した処理かという話です。
また、配列要素とプロパティとで、型がずれている可能性があります。処理によってはエラーになるでしょう。
疑問2: for inを別のもので代用した方がいいのか? もしするとしたら、何を使えばいいのか?
今度は、配列ではなく、オブジェクトのプロパティをindexingする際の話です。
- プロトタイプチェーンをさかのぼってもよいならば、for inでもよい。
- しかし、変数のスコープが広くなるのを気にするならば、Underscore.jsやUglifyJSを使おう。
というのが、答えのようです。以下の懸念を理解した上で使うなら問題ないということです。
大量のkeyを列挙することの懸念
for inでプロトタイプチェーンをさかのぼった場合、うっかり継承の深いオブジェクトをループすると、大量のkeyを列挙してしまう可能性があります。もちろん、パフォーマンスも劣化します。
具体的には、DOMをカジュアルにfor inすると酷いことになると教えてもらいました。
スコープに関する懸念
JavaScriptは、関数を使うことでしか、スコープを区切ることができません。
そのため、for inで取り出したオブジェクトを受け取るための変数は、for inが書かれたスコープ内のどこからでもアクセスできてしまいます。
これを回避するために、各種ライブラリのベンリ関数を使うのがオススメとのことでした。
Underscore.jsの_.eachや_.map、また、配列の中身を別の配列にコピーするようなときには、_.reduceを使えば、ループ時に実行する処理を関数にできるので、変数を外に漏らさずに済みます。
// 3つ目の引数の配列は、mの初期値であり、戻り値 _.reduce([1,2,3], function(m, n, i) { m[i] = n; return m; }, []);
余談
余談ですが、前回、
_.keysは、ネイティブのfor inと違って、プロトタイプチェーンをさかのぼっては見てくれないんですね。
などと書きました。が、これは、そもそもの考え方がおかしかったようです。
_.keysは、そのオブジェクトのプロパティしか見ず、ネイティブのfor inよりも探索範囲が狭いので、代用として不足ではないかと思ったので、上のように書きました。しかし、むしろ「検索先を自分自身に限定するために」使う道具だったんですね。
いろいろ教えてくださった皆さん、ありがとうございます m(_ _)m