logo hsb.horse
← スニペット一覧に戻る

Snippets

遅延初期化オブジェクト

getter を使って初回アクセス時にのみ値を生成する TypeScript パターン。メモリキャッシュより軽量な遅延評価。

公開日: 更新日:

重い初期化処理を初回アクセス時だけ実行し、以降は生成済みの値を使い回したい場面がある。メモリキャッシュほど重厚でなく、シンプルに実現するパターンだ。

コード

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; // 初回のみ読み込み実行
// ...
}

実装の仕組み

  1. 初回の getter アクセスで init 関数を実行
  2. Object.defineProperty で value をデータプロパティに上書き
  3. 2回目以降は getter が呼ばれず、直接値が返る

これにより、初回アクセス時のみ初期化コストが発生し、以降は普通のプロパティアクセスと同等のパフォーマンスになる。

メモリキャッシュとの比較

メモリキャッシュ(Map など)との違い:

  • このパターンは単一の値に特化
  • Map のようなキー管理が不要
  • ガベージコレクションも通常のオブジェクトと同じ

逆に複数の値をキャッシュしたい場合は Map や WeakMap を使うべきだ。

注意点

一度初期化されると値は変更されない。再初期化したい場合は新しい reusable インスタンスを作る必要がある。また、init 関数が例外を投げると、その後のアクセスでも同じ例外が再発生する。