Code Fix

上級

数値enumはObject.keys()で要素数が2倍になる(逆引きマッピングの副作用)

数値enumは、コンパイル後のJavaScriptオブジェクトに「メンバー名→値」だけでなく「値→メンバー名」の逆引きも自動的に持たせます。この副作用でObject.keys()を呼ぶと、メンバーの2倍の数のキーが返ってきます。

なぜエラーが出ないのか

出力: 4
(エラーなし)
TypeScriptは何も報告しません。型検査も構文も正しいためです
出力: 4
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  enum Status {
 2    Active,
 3    Inactive,
 4  }
 5  console.log(Object.keys(Status).length                                     );
                                                         ^
出力: 4

数値enumはコンパイル後、メンバー名→値だけでなく値→メンバー名の逆引きも持ちます。Object.keys().lengthはメンバー数の2倍(4)になります。

直し方: length を filter(k => Number.isNaN(Number(k))).length にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  enum Level {
 2    Low,
 3    Mid,
 4    High,
 5  }
 6  console.log(Object.keys(Level).length                                     );
                                                        ^
出力: 6

Levelは3メンバーですが、逆引きマッピングの分も合わせてObject.keys().lengthは6になります。

直し方: length を filter(k => Number.isNaN(Number(k))).length にします。

この問題を解いてみる →

パターン3

 1  enum Direction {
 2    North,
 3    South,
 4    East,
 5    West,
 6  }
 7  console.log(Object.keys(Direction).length                                     );
                                                            ^
出力: 8

Directionは4メンバーです。逆引き分も含めるとObject.keys().lengthは8になります。

直し方: length を filter(k => Number.isNaN(Number(k))).length にします。

この問題を解いてみる →

パターン4

 1  enum Weekday {
 2    Mon,
 3    Tue,
 4    Wed,
 5    Thu,
 6    Fri,
 7  }
 8  console.log(Object.keys(Weekday).length                                     );
                                                          ^
出力: 10

数値enumは逆引きマッピングも持つため、Object.keys().lengthはメンバー数の2倍(10)になります。

直し方: length を filter(k => Number.isNaN(Number(k))).length にします。

この問題を解いてみる →

パターン5

 1  enum Suit {
 2    Clubs,
 3    Diamonds,
 4    Hearts,
 5    Spades,
 6  }
 7  console.log(Object.keys(Suit).length                                     );
                                                       ^
出力: 8

Suitは4メンバーです。逆引き分も含めるとObject.keys().lengthは8になります。

直し方: length を filter(k => Number.isNaN(Number(k))).length にします。

この問題を解いてみる →

よくある誤解

「enumはただのメンバー一覧を表す型のはず」という理解は、コンパイル後の実体までは説明していません。数値enumはコンパイル時にJavaScriptの通常のオブジェクトへ変換され、逆引き用のキーも一緒に埋め込まれます。

実務での勘所

実際にコンパイルすると、enum Color { Red, Green }は{ 0: "Red", 1: "Green", Red: 0, Green: 1 }というオブジェクトになります。名前から値を引く通常の使い方だけでなく、値から名前を引きたい場面(ログ出力でColor[0]から"Red"を得るなど)のために、TypeScriptが両方向の対応を1つのオブジェクトに詰め込んでいます。この副作用を避けたい場合は、文字列enumを使うと、逆引き用のエントリは生成されずObject.keys()も本来の要素数どおりになります。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)