疑問メモ: 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