重い初期化処理を初回アクセス時だけ実行し、以降は生成済みの値を使い回したい場面がある。メモリキャッシュほど重厚でなく、シンプルに実現するパターンだ。
コード
export interface Reusable<T> { value: T;}
export function reusable<T>(init: () => T): Reusable<T> { return { get value() { const value = init(); Object.defineProperty(this, "value", { value }); return value; }, };}使用例
タイムスタンプの固定
const timestamp = reusable(() => new Date());
// 初回アクセスで Date が生成されるconsole.log(timestamp.value); // 2025-01-18T09:00:00.000Z
// 2回目以降は同じ値が返るconsole.log(timestamp.value); // 2025-01-18T09:00:00.000Z重い設定の読み込み
const config = reusable(() => { // ファイル読み込みや環境変数の解析など return loadConfigFromFile();});
// 必要になった時に初めて設定を読み込むfunction processData() { const settings = config.value; // 初回のみ読み込み実行 // ...}実装の仕組み
- 初回の getter アクセスで init 関数を実行
- Object.defineProperty で value をデータプロパティに上書き
- 2回目以降は getter が呼ばれず、直接値が返る
これにより、初回アクセス時のみ初期化コストが発生し、以降は普通のプロパティアクセスと同等のパフォーマンスになる。
メモリキャッシュとの比較
メモリキャッシュ(Map など)との違い:
- このパターンは単一の値に特化
- Map のようなキー管理が不要
- ガベージコレクションも通常のオブジェクトと同じ
逆に複数の値をキャッシュしたい場合は Map や WeakMap を使うべきだ。
注意点
一度初期化されると値は変更されない。再初期化したい場合は新しい reusable インスタンスを作る必要がある。また、init 関数が例外を投げると、その後のアクセスでも同じ例外が再発生する。
hsb.horse