Deep Freeze: Immutable Objects in JavaScript
JavaScript objects are mutable by default, meaning their properties can be changed after creation. This can lead to unexpected side effects in complex applications. A deep freeze aims to prevent any modification to an object, including its nested properties, ensuring true immutability. This challenge asks you to implement a deepFreeze function that recursively freezes all properties of an object.
Problem Description
Your task is to implement a JavaScript function called deepFreeze that takes a single argument: an object (obj). This function should recursively freeze the input object and all of its own enumerable properties, including nested objects and arrays.
Key Requirements:
- Recursive Freezing: The function must recursively freeze all properties of the input object. If a property is itself an object or an array, it should also be deeply frozen.
- Own Properties Only: The function should only freeze the object's own enumerable properties. Inherited properties should not be affected.
- Return Value: The function should return the frozen object. This is primarily for chaining or convenience, as the freezing happens in-place.
- No Modification: After calling
deepFreeze, the original object and all its nested structures should be completely immutable. Attempting to change them should result in an error or no effect (depending on strict mode).
Expected Behavior:
- Calling
Object.isFrozen(obj)on the object and its nested objects/arrays should returntrue. - Attempting to add, delete, or modify properties of the frozen object (or its nested structures) should fail. In strict mode, this will throw a
TypeError.
Edge Cases to Consider:
- Non-object inputs: What happens if
null,undefined, primitives (strings, numbers, booleans, symbols) are passed? - Empty objects/arrays: The function should handle these gracefully.
- Circular references: While
Object.freezeitself handles circular references without infinite loops, your implementation should be mindful of this. If your recursive approach isn't careful, it could lead to stack overflow.
Examples
Example 1:
const obj1 = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
};
const frozenObj1 = deepFreeze(obj1);
console.log(Object.isFrozen(frozenObj1)); // Output: true
console.log(Object.isFrozen(frozenObj1.b)); // Output: true
console.log(Object.isFrozen(frozenObj1.b.d)); // Output: true
// Attempting to modify (will throw TypeError in strict mode or fail silently otherwise)
// frozenObj1.a = 5;
// frozenObj1.b.c = 10;
// frozenObj1.b.d.push(5);
Explanation:
The obj1 is deeply frozen. All its properties (a, b) and nested properties (b.c, b.d) are now immutable. The output confirms that the original object and its nested structures are frozen.
Example 2:
const arr1 = [1, { name: "test" }, [2, 3]];
const frozenArr1 = deepFreeze(arr1);
console.log(Object.isFrozen(frozenArr1)); // Output: true
console.log(Object.isFrozen(frozenArr1[1])); // Output: true
console.log(Object.isFrozen(frozenArr1[2])); // Output: true
// Attempting to modify
// frozenArr1[0] = 100;
// frozenArr1[1].name = "modified";
// frozenArr1[2].push(4);
Explanation:
An array is treated as an object. The array arr1 and its elements (including the nested object and array) are deeply frozen.
Example 3:
const obj3 = {};
const frozenObj3 = deepFreeze(obj3);
console.log(Object.isFrozen(frozenObj3)); // Output: true
const nullInput = null;
const frozenNull = deepFreeze(nullInput); // Should likely return null without error
const primitiveInput = 5;
const frozenPrimitive = deepFreeze(primitiveInput); // Should likely return 5 without error
Explanation: Handles empty objects and non-object inputs gracefully. Non-object inputs should be returned as-is without attempting to freeze them.
Constraints
- The function must work with standard JavaScript objects, arrays, and their primitives.
- The solution should be efficient enough for reasonably sized objects and their nested structures. Avoid brute-force iteration that could lead to excessive time complexity.
- The function must use
Object.freeze()orObject.seal()(thoughfreezeis generally preferred for full immutability).Object.freeze()is the most direct way to achieve immutability. - The function should handle circular references correctly without causing infinite loops or stack overflows.
Notes
- Consider using
Object.getOwnPropertyNames()orObject.getOwnPropertySymbols()along withObject.getOwnPropertyDescriptor()to iterate over own properties. - Remember to check if a property's value is an object or an array before recursively calling
deepFreezeon it. Object.freeze()makes objects immutable:- No new properties can be added.
- Existing properties cannot be removed.
- Existing properties cannot be changed.
- The enumerability, configurability, and writability of existing properties cannot be changed.
Object.freeze()recursively freezes the top-level object but not its nested objects. This is precisely what yourdeepFreezefunction needs to accomplish.- Think about how to avoid re-processing already frozen objects or objects within a circular reference.