diff --git a/docs/ja/reference/object/deepFreeze.md b/docs/ja/reference/object/deepFreeze.md new file mode 100644 index 000000000..2d9f95fbf --- /dev/null +++ b/docs/ja/reference/object/deepFreeze.md @@ -0,0 +1,31 @@ +# deepFreeze + +オブジェクトを深くフリーズし、すべてのネストされたオブジェクトを不変にします。 + +`Object.freeze`はオブジェクトの直接のプロパティのみをフリーズしますが、 +`deepFreeze`はすべてのネストされたオブジェクトと配列を再帰的にフリーズします。 + +## インターフェース + +```typescript +function deepFreeze(obj: T): T; +``` + +### パラメータ + +- `obj` (`T`): 深くフリーズするオブジェクト。 + +### 戻り値 + +(`T`): 深くフリーズされたオブジェクト。 + +## 例 + +```typescript +import { deepFreeze } from 'es-toolkit/object'; + +const frozen = deepFreeze({ user: { name: 'Alex', age: 20 } }); + +frozen.user = {}; // strictモードではTypeError +frozen.user.age = 22; // strictモードではTypeError +``` diff --git a/docs/ko/reference/object/deepFreeze.md b/docs/ko/reference/object/deepFreeze.md new file mode 100644 index 000000000..0f7228115 --- /dev/null +++ b/docs/ko/reference/object/deepFreeze.md @@ -0,0 +1,31 @@ +# deepFreeze + +객체를 깊게 동결하여 모든 중첩된 객체를 변경 불가능하게 만들어요. + +`Object.freeze`는 객체의 직접적인 속성만 동결하지만, +`deepFreeze`는 모든 중첩된 객체와 배열을 재귀적으로 동결해요. + +## 인터페이스 + +```typescript +function deepFreeze(obj: T): T; +``` + +### 파라미터 + +- `obj` (`T`): 깊게 동결할 객체예요. + +### 반환 값 + +(`T`): 깊게 동결된 객체를 반환해요. + +## 예시 + +```typescript +import { deepFreeze } from 'es-toolkit/object'; + +const frozen = deepFreeze({ user: { name: 'Alex', age: 20 } }); + +frozen.user = {}; // strict 모드에서 TypeError +frozen.user.age = 22; // strict 모드에서 TypeError +``` diff --git a/docs/reference/object/deepFreeze.md b/docs/reference/object/deepFreeze.md new file mode 100644 index 000000000..fac2ab94d --- /dev/null +++ b/docs/reference/object/deepFreeze.md @@ -0,0 +1,31 @@ +# deepFreeze + +Deeply freezes an object, making it and all nested objects immutable. + +Unlike `Object.freeze`, which only freezes the immediate properties of an object, +`deepFreeze` recursively freezes all nested objects and arrays. + +## Signature + +```typescript +function deepFreeze(obj: T): T; +``` + +### Parameters + +- `obj` (`T`): The object to deeply freeze. + +### Returns + +(`T`): The deeply frozen object. + +## Examples + +```typescript +import { deepFreeze } from 'es-toolkit/object'; + +const frozen = deepFreeze({ user: { name: 'Alex', age: 20 } }); + +frozen.user = {}; // TypeError in strict mode +frozen.user.age = 22; // TypeError in strict mode +``` diff --git a/docs/zh_hans/reference/object/deepFreeze.md b/docs/zh_hans/reference/object/deepFreeze.md new file mode 100644 index 000000000..ae1c57744 --- /dev/null +++ b/docs/zh_hans/reference/object/deepFreeze.md @@ -0,0 +1,31 @@ +# deepFreeze + +深度冻结对象,使其和所有嵌套的对象都不可变。 + +与 `Object.freeze` 只冻结对象的直接属性不同, +`deepFreeze` 递归地冻结所有嵌套的对象和数组。 + +## 签名 + +```typescript +function deepFreeze(obj: T): T; +``` + +### 参数 + +- `obj` (`T`): 要深度冻结的对象。 + +### 返回值 + +(`T`): 深度冻结后的对象。 + +## 示例 + +```typescript +import { deepFreeze } from 'es-toolkit/object'; + +const frozen = deepFreeze({ user: { name: 'Alex', age: 20 } }); + +frozen.user = {}; // 严格模式下抛出 TypeError +frozen.user.age = 22; // 严格模式下抛出 TypeError +``` diff --git a/src/object/deepFreeze.spec.ts b/src/object/deepFreeze.spec.ts new file mode 100644 index 000000000..0d7f893b7 --- /dev/null +++ b/src/object/deepFreeze.spec.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from 'vitest'; +import { deepFreeze } from './deepFreeze'; + +describe('deepFreeze', () => { + it('should freeze nested objects', () => { + const obj = deepFreeze({ user: { name: 'Alex', age: 20 } }); + + expect(Object.isFrozen(obj)).toBe(true); + expect(Object.isFrozen(obj.user)).toBe(true); + }); + + it('should freeze nested arrays', () => { + const obj = deepFreeze({ items: [1, 2, { nested: true }] }); + + expect(Object.isFrozen(obj)).toBe(true); + expect(Object.isFrozen(obj.items)).toBe(true); + expect(Object.isFrozen(obj.items[2])).toBe(true); + }); + + it('should return the same object', () => { + const obj = { a: 1 }; + expect(deepFreeze(obj)).toBe(obj); + }); + + it('should handle primitives', () => { + expect(deepFreeze(42)).toBe(42); + expect(deepFreeze('hello')).toBe('hello'); + expect(deepFreeze(null)).toBe(null); + expect(deepFreeze(undefined)).toBe(undefined); + expect(deepFreeze(true)).toBe(true); + }); + + it('should handle deeply nested objects', () => { + const obj = deepFreeze({ a: { b: { c: { d: 1 } } } }); + + expect(Object.isFrozen(obj.a)).toBe(true); + expect(Object.isFrozen(obj.a.b)).toBe(true); + expect(Object.isFrozen(obj.a.b.c)).toBe(true); + }); + + it('should handle circular references', () => { + const obj: Record = { a: 1 }; + obj.self = obj; + + const frozen = deepFreeze(obj); + + expect(Object.isFrozen(frozen)).toBe(true); + expect(frozen.self).toBe(frozen); + }); + + it('should handle an empty object', () => { + const obj = deepFreeze({}); + expect(Object.isFrozen(obj)).toBe(true); + }); + + it('should freeze arrays directly', () => { + const arr = deepFreeze([1, [2, 3], { a: 4 }]); + + expect(Object.isFrozen(arr)).toBe(true); + expect(Object.isFrozen(arr[1])).toBe(true); + expect(Object.isFrozen(arr[2])).toBe(true); + }); + + it('should freeze Date and RegExp objects', () => { + const obj = deepFreeze({ date: new Date(), regex: /test/ }); + + expect(Object.isFrozen(obj.date)).toBe(true); + expect(Object.isFrozen(obj.regex)).toBe(true); + }); + + it('should prevent modifications in strict mode', () => { + 'use strict'; + const obj = deepFreeze({ user: { name: 'Alex', age: 20 } }); + + expect(() => { + (obj as any).user = {}; + }).toThrow(); + + expect(() => { + (obj as any).user.age = 22; + }).toThrow(); + }); +}); diff --git a/src/object/deepFreeze.ts b/src/object/deepFreeze.ts new file mode 100644 index 000000000..1f3b5ad90 --- /dev/null +++ b/src/object/deepFreeze.ts @@ -0,0 +1,35 @@ +/** + * Deeply freezes an object, making it and all nested objects immutable. + * + * Unlike `Object.freeze`, which only freezes the immediate properties of an object, + * `deepFreeze` recursively freezes all nested objects and arrays. + * + * @template T - The type of the object. + * @param {T} obj - The object to deeply freeze. + * @returns {T} The deeply frozen object. + * + * @example + * const frozen = deepFreeze({ user: { name: "Alex", age: 20 } }); + * + * frozen.user = {}; // TypeError in strict mode + * frozen.user.age = 22; // TypeError in strict mode + */ +export function deepFreeze(obj: T): T { + if (obj === null || typeof obj !== 'object') { + return obj; + } + + Object.freeze(obj); + + const keys = Object.keys(obj) as Array; + + for (let i = 0; i < keys.length; i++) { + const value = obj[keys[i]]; + + if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) { + deepFreeze(value); + } + } + + return obj; +} diff --git a/src/object/index.ts b/src/object/index.ts index 1fbcd9a83..7b521b02a 100644 --- a/src/object/index.ts +++ b/src/object/index.ts @@ -1,6 +1,7 @@ export { clone } from './clone.ts'; export { cloneDeep } from './cloneDeep.ts'; export { cloneDeepWith } from './cloneDeepWith.ts'; +export { deepFreeze } from './deepFreeze.ts'; export { findKey } from './findKey.ts'; export { flattenObject } from './flattenObject.ts'; export { invert } from './invert.ts';