🧊

【Typescript】再帰的 Object.freeze

2023/11/23に公開

概要

Object.freeze を使うことでオブジェクトの既存のプロパティを編集不可能にすることができます.
ただし, Object.freeze はオブジェクトのネストされたプロパティには影響を与えないので, 再帰的に実行する必要があります.
オブジェクトのネストされたプロパティを再帰的に freeze し、変更不能にするための関数を作成しました.

実装

freeze できるのは基本的に obj === null || (typeof obj !== "object" && typeof obj !== "function") を満たすものです.
そのため, 関数内でチェックを行います.

ブラウザや Node.js などの実行環境によって提供されるホストオブジェクト(例えば、ブラウザの window オブジェクトや DOM 要素)は freeze しようとするとエラーになる場合があります.
そのため, try-catch を入れて Object.freeze のエラーは握り潰しています.

また, プロパティの循環参照がある場合に無限ループが発生するため, その対策も入れています.

export const deepFreeze = <T>(obj: T, depth = Infinity): Readonly<T> => {
  // If obj is null (a kind of object), undefined, primitive, or already frozen, return as it is
  if (
    obj === null ||
    (typeof obj !== "object" && typeof obj !== "function") ||
    Object.isFrozen(obj)
  ) {
    return obj;
  }

  if (depth <= 0) {
    console.warn("Maximum recursion depth exceeded in deepFreeze");
    return obj;
  }

  try {
    if (Array.isArray(obj)) {
      obj.forEach((element) => deepFreeze(element, depth - 1));
    } else {
      Object.keys(obj).forEach((key) => {
        const prop = (obj as { [key: string]: unknown })[key];
        // To prevent circular reference, check Object.isFrozen
        if (!Object.isFrozen(prop)) {
          deepFreeze(prop, depth - 1);
        }
      });
    }

    // Freeze self (no-op if already frozen)
    return Object.freeze(obj) as Readonly<T>;
  } catch (error) {
    console.error("Error occurred in deepFreeze:", error);
    return obj;
  }
};

Discussion