How to Prevent Mutating an Object in JavaScript
If we don’t need a copy of an object and just want to prevent mutating it, JavaScript provides three helpful methods: Object.preventExtensions(), Object.seal(), and Object.freeze().
Object.preventExtensions()
This method allows us to modify values in the object and delete properties, but it prevents us from adding properties.
const fruits = {"apples": 3, "bananas": 4};
Object.preventExtensions(fruits);
fruits.papayas = 1;
console.log(fruits); // {"apples": 3, "bananas": 4};
Note that there is no error message. The three methods mentioned above will fail silently! If we try to extend an onject that has been frozen with Object.preventExtensions() we will not see an error message when attempting to add a property. The object remains unchanged, and there is no explanation. To check if we are allowed to add properties to an object, we can call Object.isExtensible().
Object.seal()
This method allows us to modify values in the object, but it prevents us from adding or deleting properties. To check if an object has been sealed, we can call Object.isSealed().
const fruits = {"apples": 3, "bananas": 4};
Object.seal(fruits);
delete fruits.apples;
console.log(fruits); // {"apples": 3, "bananas": 4};
Object.freeze()
This method prevents us making any changes to the object. To check if an object has been frozen, we can call Object.isFrozen().
const fruits = {"apples": 3, "bananas": 4};
Object.seal(fruits);
delete fruits.apples;
console.log(fruits); // {"apples": 3, "bananas": 4};
Deep freeze
Unfortunately, the three methods above only operate on the first level of the object. If our object contains a nested object, we are still able to modify values and add or delete properties. For a deep freeze, we need to apply our methods recursively to reach nested objects.
This example shows the vulnerability of the shallow freeze:
const food = {"cereal": 1, "fruits": {"apples": 3, "bananas": 4}};
Object.freeze(food);
delete food.fruits.apples;
console.log(food); // {"cereal": 1, "fruits": {"bananas": 4}}
A recursive deep freeze prevents mutation:
function deepFreeze(obj) {
// this guard clause stops recursion when finished
if (obj === null || typeof obj !== 'object') return obj;
// recursive freeze
Object.keys(obj).forEach((key) => {
deepFreeze(obj[key]);
});
return Object.freeze(obj);
};
const food = {"cereal": 1, "fruits": {"apples": 3, "bananas": 4}};
deepFreeze(food);
delete food.fruits.apples;
console.log(food); // {"cereal": 1, "fruits": {"apples": 3, "bananas": 4}}
Related posts:
Helpful references:
Have feedback on this topic?