自分の速さで

調べたこと、忘れそうなことをゆるゆると書いていく

TypeScriptのenumをfor-in文で使用した時の挙動について

はじめに

最近Cordova+TypeScriptでハイブリッドアプリを作っているのですが、列挙型をfor-inしたときにハマったのでメモ。

現象

試しにこんなプログラムがあったとして

enum Hoge {
    Foo,
    Bar
}

for(var hoge in Hoge) {
    console.log(hoge);
}

こう出力されてほしいのですが

Foo
Bar

実際はこう出力されます

0
1
Foo
Bar

解説

何でこんな挙動になるか調べたら下記にヒットしました。

stackoverflow.com

enumjavascriptコンパイルすると

var Hoge;
(function (Hoge) {
    Hoge[Hoge["Foo"] = 0] = "Foo";
    Hoge[Hoge["Bar"] = 1] = "Bar";
})(Hoge || (Hoge = {});

と変換されるため、Hogeオブジェクトは以下となります。

Object {0: "Foo", 1: "Bar", Foo: 0, Bar: 1}

これによって、以下のように列挙型の値から文字列を取得したり、逆に文字列から列挙型の値を取得できるようになるわけですが、これのせいでfor-inが想定した結果を出力してくれないのです。

console.log(Hoge[0])        // "Foo"
console.log(Hoge[Hoge.Foo]) // "Foo"
console.log(Hoge["Bar"])    // 1

解決策

要はキーまたは値のうち、取りたくないほうを無視すればいいわけなので、stackoverflowでも示されている通り

文字列を取得したい場合

for(var key in Hoge) {
    if(!isNaN(key)) {
        console.log(key);
    }
}

列挙型の値を取得したい場合

for(var value in Hoge ){
    if(isNaN(value)) {
        console.log(value);
    }
}

とすればOKです。それぞれ出力結果は

0
1
Foo
Bar

となります。また、
TypeScript クイックガイド - phyzkit.net
にも書いてありますが、for文で繰り返すか、linq.jsのEnumerable.RangeToを使う手もあります。こっちの方がすっきりしますが、列挙型の値を自分で設定している、かつ、値に欠番がある場合は使えないので注意が必要です。

for(var i: Hoge = Hoge.Foo; i <= Hoge.Bar; i++){
    console.log(Hoge[i]);
}
Enumerable.RangeTo(Hoge.Foo, Hoge.Bar)
.ForEach(x => {
    console.log(Hoge[x]);
});