疑問メモ: JavaScriptで配列やオブジェクトのキーを反復するイディオム
「JavaScriptではネイティブのfor文を使わない方がいいよ!」と教えてもらいました。
ネイティブのforの代わりに、Array.forEachか、Underscore.jsにあるベンリな関数を使おう、とのことでした。そうすればスコープを限定できるから、というのが理由だったと思います。
組込みの繰り返し構文の使用が非推奨の言語なんて、初めて聞いたのでびっくりです。
というわけで、for inやArrays.forEachを試していたところ、モヤモヤと分からないことがあるのでメモします。
(追記)解決編を書きました。http://d.hatena.ne.jp/torazuka/20130304/for
配列の反復には、for inもArray.forEachも使える(の?
for in
var foo = {}; var hoge = [1, 3, 5]; for(var n in hoge){ foo[n] = hoge[n]; } console.log(foo);
Javaの感覚だと、nには配列の各要素が入っていて欲しいですが、実際には配列のindexが入ります。
結果はこうなります。
{ '0': 1, '1': 3, '2': 5 }
やりたいことはできたけど、こういう使い方をするものなのか分かりません。
配列を反復したいときは、ふつうのforを使う気がします。どうなんでしょう?
Array.forEach
var foo = {}; var hoge = [1, 3, 5]; var fill = function(element, index, array){ foo[index] = element; }; hoge.forEach(fill); console.log(foo);
forEachの引数に、各要素を処理する関数を渡します。その関数(上のコードだとfillと名づけたもの)のシグネチャが、規格で決められているんですね。
結果は、for inと同じになります。
{ '0': 1, '1': 3, '2': 5 }
オブジェクトのキーの反復には、for inは使えるがArray.forEachは使えない
for inを使って、あるオブジェクトのプロパティを、別のオブジェクトにコピーする処理について、考えます。
# またしてもJavaScript The Good Partsの写経です。
for in
var assert = require('assert'); var _ = require("../lib/underscore-min.js"); describe('Object', function(){ it('3.7. should return properties', function(){ var stooge = { "first-name" : "Jarome", "last-name" : "Howard" }; if(typeof Object.create !== 'function') { Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); }; } // stoogeのプロパティを継承したanother_stoogeを作って、いくつかプロパティを追加 var another_stooge = Object.create(stooge); another_stooge['first-name'] = "Harry"; another_stooge['middle-name'] = "Moses"; another_stooge.nickname = "Moe"; stooge.profession = "actor"; // another_stoogeのfunction以外のプロパティをfooにコピーする var foo = {}; // need init var name; for (name in another_stooge) { if(typeof another_stooge[name] !== 'function'){ foo[name] = another_stooge[name]; } } assert(foo["first-name"] === "Harry"); // stoogeのプロパティ(another_stoogeで上書き済み) assert(foo["middle-name"] === "Moses"); // another_stoogeのプロパティ assert(foo["nickname"] === "Moe"); // another_stoogeのプロパティ assert(foo["last-name"] === "Howard"); // stoogeのプロパティ assert(foo["profession"] === "actor"); // stoogeに後から足したプロパティ(another_stoogeから参照可能) }) });
このfor inを、何か別のもので代用した方がいいのか? もしするとしたら、何を使えばいいのか? というのが疑問。
Arrays.forEach
最初はこんなふうに書いたものの、(当然)エラーになりました。
var foo = {}; var fill = function(element, index, array){ foo[index] = element; }; another_stooge.forEach(fill);
TypeError: Object #<Object> has no method 'forEach'
- forEachは配列のための関数
- 配列はオブジェクトだが、オブジェクトはかならずしも配列ではない
ってことですよね。
ついでに、配列の反復とオブジェクトのキーの反復を混同していたことに、JavaScriptテクニックバイブルという本のforEachの項目を読んで気づきました(^^; 素晴らしい本ですね。
for each in
WikipediaのForeach文(http://ja.wikipedia.org/wiki/Foreach%E6%96%87)に載っていたけど、自分の環境では動かなかったので省略。実装されてる処理系もあるのかな。
for each ( 変数 in オブジェクト ) {
文
}
(ただのfor inと何が違うんだろう?)
Underscore.jsのkeys
Underscore.jsには、オブジェクトのキーを抜き出す関数があるようなので、使ってみました。
var foo = {}; var keys = _.keys(another_stooge); _.each(keys, function(name){ if(typeof another_stooge[name] !== 'function'){ foo[name] = another_stooge[name]; } }); assert(foo["first-name"] === "Harry"); assert(foo["middle-name"] === "Moses"); assert(foo["nickname"] === "Moe"); assert(foo["last-name"] === "Howard"); // failed! assert(foo["profession"] === "actor"); // failed!
おっと。_.keysは、ネイティブのfor inと違って、プロトタイプチェーンをさかのぼっては見てくれないんですね。
stoogeに元からあるプロパティだけが、コピーされました。
疑問まとめ
- for inは配列の反復に使ってもいいの?
- オブジェクトのキーの反復に使う第一候補は、for inでいい? 違う場合、何を使うのが定石?
分かったら追記。(追記)解決編を書きました。http://d.hatena.ne.jp/torazuka/20130304/for