JavaScript enumの代替方法 - 定数オブジェクトの活用法

JavaScriptでenumを実現する定数オブジェクトの作成方法と活用法を解説。TypeScriptのenumとの比較や実践的な使用例も紹介します。

Learning Next 運営
25 分で読めます

JavaScript enumの代替方法 - 定数オブジェクトの活用法

みなさん、JavaScriptでプログラミングをしていて困ったことはありませんか?

「状態やタイプを定数で管理したい!」 「JavaScriptにはenumがないけど、どうやって実現するの?」 こんな疑問を持っている方、きっと多いですよね。

実は、JavaScriptでも定数オブジェクトを使えば、enum(列挙型)と同じような機能を作れるんです! この記事では、JavaScriptでenumの代替となる定数オブジェクトの作成方法と活用法について、初心者の方にも分かりやすく解説します。

TypeScriptのenumとの比較から実践的な使用例まで、効率的な定数管理のテクニックを一緒に学んでいきましょう。

enumって何?基本を知ろう

定数をまとめて管理する仕組み

enumは、関連する定数をグループ化して管理する仕組みです。

簡単に言うと、「似たような定数を一つの場所にまとめて、名前を付けて管理する」機能なんです。 これにより、コードが読みやすくなり、定数を変更する時も楽になります。

他の言語でのenum例

TypeScriptやJavaでは、こんな風にenumを使います。

// TypeScriptでのenum
enum Color {
RED = 'red',
GREEN = 'green',
BLUE = 'blue'
}
// 使用例
let selectedColor = Color.RED;

こんな便利な構文が、残念ながらJavaScriptには標準では存在しません。 でも大丈夫です!代替手段があるんです。

JavaScriptでenumを作ってみよう

一番シンプルな方法

JavaScriptでenumを実現する最もシンプルな方法から始めましょう。

// 基本的な定数オブジェクト
const Colors = {
RED: 'red',
GREEN: 'green',
BLUE: 'blue'
};
// 使用例
console.log(Colors.RED); // 'red'
let userColor = Colors.BLUE;

オブジェクトのプロパティとして定数を定義することで、enum風の使い方ができます。 とってもシンプルですよね!

Object.freeze()で安全にしよう

定数オブジェクトが間違って変更されないようにする方法があります。

const Status = Object.freeze({
PENDING: 'pending',
SUCCESS: 'success',
ERROR: 'error'
});
// 使用例
console.log(Status.PENDING); // 'pending'
// 変更を試みても無効(エラーにはならない)
Status.PENDING = 'changed';
console.log(Status.PENDING); // 'pending'(変更されない)

Object.freeze()を使うことで、定数の値が意図せず変更されることを防げます。 これで安心ですね。

数値型enumを作ってみよう

連番を使った数値enum

数値の連番を使ったenum風の実装です。

const Priority = Object.freeze({
LOW: 1,
MEDIUM: 2,
HIGH: 3,
URGENT: 4
});
// 使用例
function processTasks(tasks) {
tasks.forEach(task => {
if (task.priority === Priority.URGENT) {
console.log(`緊急タスク: ${task.title}`);
} else if (task.priority >= Priority.HIGH) {
console.log(`高優先度タスク: ${task.title}`);
}
});
}
let tasks = [
{ title: 'バグ修正', priority: Priority.URGENT },
{ title: '機能追加', priority: Priority.MEDIUM },
{ title: 'テスト作成', priority: Priority.HIGH }
];
processTasks(tasks);

数値で管理することで、優先度の比較などが簡単にできます。

ビットフラグパターン

複数の状態を組み合わせられるビットフラグ形式も作れます。

const Permission = Object.freeze({
READ: 1, // 001
WRITE: 2, // 010
EXECUTE: 4 // 100
});
// 複数の権限を組み合わせ
const ADMIN_PERMISSION = Permission.READ | Permission.WRITE | Permission.EXECUTE;
const EDITOR_PERMISSION = Permission.READ | Permission.WRITE;
// 権限チェック関数
function hasPermission(userPermission, requiredPermission) {
return (userPermission & requiredPermission) === requiredPermission;
}
// 使用例
console.log(hasPermission(ADMIN_PERMISSION, Permission.READ)); // true
console.log(hasPermission(EDITOR_PERMISSION, Permission.EXECUTE)); // false

ちょっと高度ですが、複数の権限を効率的に管理できる便利な方法です。

文字列型enumの実践例

API状態管理

WebアプリでよくあるAPI処理の状態を管理してみましょう。

const ApiStatus = Object.freeze({
IDLE: 'idle',
LOADING: 'loading',
SUCCESS: 'success',
ERROR: 'error'
});
// 状態に応じたメッセージ
const StatusMessage = Object.freeze({
[ApiStatus.IDLE]: '待機中',
[ApiStatus.LOADING]: '読み込み中...',
[ApiStatus.SUCCESS]: '完了しました',
[ApiStatus.ERROR]: 'エラーが発生しました'
});
// 使用例
function handleApiCall(status) {
switch (status) {
case ApiStatus.LOADING:
console.log('ローディング表示');
break;
case ApiStatus.SUCCESS:
console.log('成功メッセージ表示');
break;
case ApiStatus.ERROR:
console.log('エラーメッセージ表示');
break;
default:
console.log('通常表示');
}
return StatusMessage[status];
}
console.log(handleApiCall(ApiStatus.LOADING)); // '読み込み中...'

API処理の状態がとても管理しやすくなりますね。

ユーザーロール管理

ユーザーの権限レベルを管理する例も見てみましょう。

const UserRole = Object.freeze({
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
GUEST: 'guest'
});
// 権限レベルを数値で管理
const RoleLevel = Object.freeze({
[UserRole.GUEST]: 0,
[UserRole.VIEWER]: 1,
[UserRole.EDITOR]: 2,
[UserRole.ADMIN]: 3
});
// 権限チェック関数
function canAccess(userRole, requiredRole) {
return RoleLevel[userRole] >= RoleLevel[requiredRole];
}
// 使用例
console.log(canAccess(UserRole.EDITOR, UserRole.VIEWER)); // true
console.log(canAccess(UserRole.GUEST, UserRole.ADMIN)); // false

ユーザーの権限管理がとてもスッキリしますね。

高度なenum実装パターン

メソッド付きenum

定数だけでなく、関連するメソッドも含むenum実装です。

const TaskStatus = Object.freeze({
TODO: 'todo',
IN_PROGRESS: 'in_progress',
REVIEW: 'review',
DONE: 'done',
// 状態の表示名を取得
getDisplayName(status) {
const displayNames = {
[this.TODO]: '未着手',
[this.IN_PROGRESS]: '進行中',
[this.REVIEW]: 'レビュー中',
[this.DONE]: '完了'
};
return displayNames[status] || '不明';
},
// 次の状態を取得
getNextStatus(currentStatus) {
const transitions = {
[this.TODO]: this.IN_PROGRESS,
[this.IN_PROGRESS]: this.REVIEW,
[this.REVIEW]: this.DONE,
[this.DONE]: this.DONE
};
return transitions[currentStatus] || currentStatus;
},
// 状態変更可能かチェック
canTransition(from, to) {
const validTransitions = {
[this.TODO]: [this.IN_PROGRESS],
[this.IN_PROGRESS]: [this.REVIEW, this.TODO],
[this.REVIEW]: [this.DONE, this.IN_PROGRESS],
[this.DONE]: []
};
return validTransitions[from]?.includes(to) || false;
}
});

使ってみましょう。

// 使用例
console.log(TaskStatus.getDisplayName(TaskStatus.TODO)); // '未着手'
console.log(TaskStatus.getNextStatus(TaskStatus.TODO)); // 'in_progress'
console.log(TaskStatus.canTransition(TaskStatus.TODO, TaskStatus.IN_PROGRESS)); // true

メソッドが付いていることで、より便利で安全な状態管理ができます。

階層構造のenum

カテゴリーを階層化したenum実装も作れます。

const Category = Object.freeze({
ELECTRONICS: Object.freeze({
COMPUTER: Object.freeze({
LAPTOP: 'laptop',
DESKTOP: 'desktop',
TABLET: 'tablet'
}),
PHONE: Object.freeze({
SMARTPHONE: 'smartphone',
FEATURE_PHONE: 'feature_phone'
})
}),
CLOTHING: Object.freeze({
MENS: Object.freeze({
SHIRT: 'mens_shirt',
PANTS: 'mens_pants'
}),
WOMENS: Object.freeze({
DRESS: 'womens_dress',
SKIRT: 'womens_skirt'
})
})
});
// 使用例
console.log(Category.ELECTRONICS.COMPUTER.LAPTOP); // 'laptop'
console.log(Category.CLOTHING.MENS.SHIRT); // 'mens_shirt'

階層になっていることで、商品カテゴリーなどの複雑な分類も管理しやすくなります。

実際のアプリで使える活用例

フォームバリデーション

フォームの入力検証でenum活用してみましょう。

const ValidationRule = Object.freeze({
REQUIRED: 'required',
EMAIL: 'email',
MIN_LENGTH: 'min_length',
MAX_LENGTH: 'max_length',
PATTERN: 'pattern'
});
const ValidationMessage = Object.freeze({
[ValidationRule.REQUIRED]: '必須項目です',
[ValidationRule.EMAIL]: '正しいメールアドレスを入力してください',
[ValidationRule.MIN_LENGTH]: '文字数が不足しています',
[ValidationRule.MAX_LENGTH]: '文字数が多すぎます',
[ValidationRule.PATTERN]: '形式が正しくありません'
});
// バリデーション関数
function validateField(value, rules) {
let errors = [];
rules.forEach(rule => {
switch (rule.type) {
case ValidationRule.REQUIRED:
if (!value || value.trim() === '') {
errors.push(ValidationMessage[ValidationRule.REQUIRED]);
}
break;
case ValidationRule.EMAIL:
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (value && !emailPattern.test(value)) {
errors.push(ValidationMessage[ValidationRule.EMAIL]);
}
break;
case ValidationRule.MIN_LENGTH:
if (value && value.length < rule.value) {
errors.push(`${rule.value}文字以上で入力してください`);
}
break;
}
});
return errors;
}
// 使用例
let emailErrors = validateField('invalid-email', [
{ type: ValidationRule.REQUIRED },
{ type: ValidationRule.EMAIL }
]);
console.log(emailErrors); // ['正しいメールアドレスを入力してください']

バリデーションルールが定数として管理されているので、とても分かりやすいですね。

アプリ設定管理

アプリケーションの設定を管理するシステムです。

const AppConfig = Object.freeze({
THEME: Object.freeze({
LIGHT: 'light',
DARK: 'dark',
AUTO: 'auto'
}),
LANGUAGE: Object.freeze({
JA: 'ja',
EN: 'en',
ZH: 'zh'
}),
NOTIFICATION: Object.freeze({
ALL: 'all',
IMPORTANT: 'important',
NONE: 'none'
})
});
// 設定の初期値
const DefaultConfig = Object.freeze({
theme: AppConfig.THEME.LIGHT,
language: AppConfig.LANGUAGE.JA,
notification: AppConfig.NOTIFICATION.ALL
});
// 設定管理クラス
class ConfigManager {
constructor() {
this.config = { ...DefaultConfig };
}
setTheme(theme) {
if (Object.values(AppConfig.THEME).includes(theme)) {
this.config.theme = theme;
console.log(`テーマを${theme}に変更しました`);
} else {
console.error('無効なテーマです');
}
}
setLanguage(language) {
if (Object.values(AppConfig.LANGUAGE).includes(language)) {
this.config.language = language;
console.log(`言語を${language}に変更しました`);
} else {
console.error('無効な言語です');
}
}
getConfig() {
return { ...this.config };
}
}
// 使用例
let configManager = new ConfigManager();
configManager.setTheme(AppConfig.THEME.DARK);
configManager.setLanguage(AppConfig.LANGUAGE.EN);
console.log(configManager.getConfig());

設定値が定数として管理されているので、無効な値の設定を防げます。

Symbolを使った高度な実装

より安全なenum

Symbolを使うことで、より安全なenum実装ができます。

// Symbolを使った実装
const LogLevel = Object.freeze({
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
ERROR: Symbol('error')
});
// 表示名とのマッピング
const LogLevelName = Object.freeze({
[LogLevel.DEBUG]: 'DEBUG',
[LogLevel.INFO]: 'INFO',
[LogLevel.WARN]: 'WARN',
[LogLevel.ERROR]: 'ERROR'
});
// ログレベルの重要度
const LogLevelPriority = Object.freeze({
[LogLevel.DEBUG]: 0,
[LogLevel.INFO]: 1,
[LogLevel.WARN]: 2,
[LogLevel.ERROR]: 3
});
// ログクラス
class Logger {
constructor(minLevel = LogLevel.INFO) {
this.minLevel = minLevel;
}
log(level, message) {
if (LogLevelPriority[level] >= LogLevelPriority[this.minLevel]) {
console.log(`[${LogLevelName[level]}] ${message}`);
}
}
debug(message) {
this.log(LogLevel.DEBUG, message);
}
info(message) {
this.log(LogLevel.INFO, message);
}
warn(message) {
this.log(LogLevel.WARN, message);
}
error(message) {
this.log(LogLevel.ERROR, message);
}
}
// 使用例
let logger = new Logger(LogLevel.WARN);
logger.debug('これは表示されません'); // 表示されない
logger.info('これも表示されません'); // 表示されない
logger.warn('警告メッセージ'); // [WARN] 警告メッセージ
logger.error('エラーメッセージ'); // [ERROR] エラーメッセージ

Symbolを使うことで、外部から値を操作される心配がありません。

TypeScriptとの比較

TypeScriptのenum

TypeScriptでは、こんな風にenumを書きます。

// TypeScriptでのenum
enum Color {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
// 数値enum
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 使用例
let userColor: Color = Color.Red;
let direction: Direction = Direction.Up;

JavaScript版での対応

同じ機能をJavaScriptで実装すると、こうなります。

// JavaScript版でのenum
const Color = Object.freeze({
RED: 'red',
GREEN: 'green',
BLUE: 'blue'
});
const Direction = Object.freeze({
UP: 0,
DOWN: 1,
LEFT: 2,
RIGHT: 3
});
// 型チェック関数を追加
function isValidColor(color) {
return Object.values(Color).includes(color);
}
function isValidDirection(direction) {
return Object.values(Direction).includes(direction);
}
// 使用例
let userColor = Color.RED;
let direction = Direction.UP;
console.log(isValidColor('red')); // true
console.log(isValidColor('purple')); // false

JavaScriptでも、関数を追加することで型チェック機能を実現できます。

まとめ

JavaScriptでのenum代替方法について学びました。

重要なポイント

  • Object.freeze()で不変な定数オブジェクトを作成
  • 数値型と文字列型を適切に使い分ける
  • メソッドを含む高度なenum実装も可能
  • Symbolを使ってより安全性を向上できる

実用的な活用場面

  • API状態の管理
  • ユーザー権限の管理
  • フォームバリデーション
  • アプリケーション設定
  • ログレベルの管理

TypeScriptとの違い

  • JavaScriptは自分で型チェック関数を作る必要がある
  • でも、基本的な機能は十分に実現できる

定数オブジェクトを適切に活用することで、マジックナンバーやハードコードされた文字列を排除し、変更に強いコードを書けるようになります。

特に状態管理やAPI処理、設定管理において、enumパターンはとても有効な設計手法です。 最初はシンプルな定数オブジェクトから始めて、だんだんと高度な機能にもチャレンジしてみてくださいね。

ぜひ今日から、これらの技術をマスターして、より保守性の高いJavaScriptプログラムを作ってみませんか?

関連記事