Reputation: 18097
Often you have to pass an object as parameter. Functions accessing those objects are pass by reference and can change original object. Which depending on situation can be unwanted outcome. So is there a way to freeze object. I know about Object.freeze()
But it doesn't affect objects/arrays inside it.
For example
a = { arr: [1,2,3]};
Object.freeze(a);
a.arr.push(4); // still works
Upvotes: 4
Views: 2101
Reputation: 13673
My 5 cents, since none of the answers here or in the duplicate question would provide a solution that would be both correct and efficient at same time.
Based on the MDN documentation of Object.freeze(), but modified to account for circular references.
function deepFreeze(object) {
const occurrences = new WeakSet();
function deepFreezeCircularlySafe(object) {
if (occurrences.has(object)) {
return object;
}
occurrences.add(object);
// Retrieve the property names defined on object
const propNames = Reflect.ownKeys(object);
// Freeze properties before freezing self
for (const name of propNames) {
const value = object[name];
if ((value && typeof value === "object") || typeof value === "function") {
deepFreezeCircularlySafe(value);
}
}
return Object.freeze(object);
}
return deepFreezeCircularlySafe(object);
}
The Object.isFrozen()
solution that many promote will actually give false positives on input that already had some of the nodes frozen before the call to deepFreeze()
and therefore is not safe to use as it does not guarantee that all of the sub-nodes would really be frozen.
Upvotes: 0
Reputation: 478
Solution from the MDN documentation of Object.freeze()
:
function deepFreeze(object) {
// Retrieve the property names defined on object
const propNames = Reflect.ownKeys(object);
// Freeze properties before freezing self
for (const name of propNames) {
const value = object[name];
if ((value && typeof value === "object") || typeof value === "function") {
deepFreeze(value);
}
}
return Object.freeze(object);
}
let a = { arr: [1,2,3]};
deepFreeze(a);
a.arr.push(4); // TypeError: Cannot add property 3, object is not extensible
Thank you :)
Upvotes: 2
Reputation: 19
You might want to consider immer. It's an excellent tool to create immutable Js data structures.
In your case, that would be:
const a = produce({ arr: [1,2,3] }, () => {})
The () => {}
is necessary because produce
expects a function that mutates the first param, but there is no mutation required here.
If you are trying to freeze a couple of data structures and won't use immutability otherwise, it might not be worth the cost of an extra third-party library. And the above solutions will be enough.
Upvotes: -1
Reputation: 19295
Another approach is to use a Proxy. Instead of deep freezing the object you get a proxy to the object that forbids writing:
const immutableProxy = o => {
if (o===null || typeof o !== 'object') return o
return new Proxy(o, {
get(obj, prop) {
return immutableProxy(obj[prop])
},
set(obj, prop) {
throw new Error(`Can not set prop ${prop} on immutable object`)
}
})
}
Upvotes: 0
Reputation: 547
To deep-freeze all enumerable properties (ES2015+):
// Recursively freeze an object
const deepFreeze = x => {
Object.freeze(x)
Object.values(x).forEach(deepFreeze)
}
If you have circular references:
// Recursively freeze an object with circular references
const deepFreeze = x => {
Object.freeze(x)
Object.values(x).filter(x => !Object.isFrozen(x)).forEach(deepFreeze)
}
If you also have to deep-freeze any shallow-frozen stuff (slightly slower):
// Recursively deep freeze an object with circular references
const deepFreeze = (x, frozen = []) => {
if (frozen.includes(x)) return null
frozen.push(x)
Object.freeze(x)
Object.values(x).forEach(x => deepFreeze(x, frozen))
}
tldr; Two-liner:
const deepFreeze = (x, frozen = []) => frozen.includes(x) ||
frozen.push(Object.freeze(x)) && Object.values(x).forEach(x => deepFreeze(x, frozen))
Upvotes: 2
Reputation: 44105
You can make a very simple recursive solution like so:
let a = {
arr: [1, 2, 3],
name: "A. Bc"
};
const deepFreeze = o => {
for (let [key, value] of Object.entries(o)) {
if (o.hasOwnProperty(key) && typeof value == "object") {
deepFreeze(value);
}
}
Object.freeze(o);
return o;
}
deepFreeze(a);
try {
a.arr.push(4);
} catch(e) {
console.log("Error: ", e);
}
console.log(a);
Upvotes: 1
Reputation: 6491
If you look at MDN, there is a function there that suggests deepFreeze functionality however it is not stack safe. I personally have an ES5 version to async iterate. For ES6 something along these lines might work, I did not test it thoroughly though:
function deepFreeze(o,promises,oldestParent){
promises = promises || [];
oldestParent = oldestParent || o;
promises.push(
Promise.resolve().then(function(){
Object.values(Object.freeze(o)).forEach(function(d,i){
typeof d === "object" && deepFreeze(d,promises,oldestParent);
});
return oldestParent;
})
);
return Promise.all(promises).then((a)=>a[0]);
}
var o = {a:3,b:{test:1,test2:2},c:1};
deepFreeze(o).then(function(x){console.log(x)}); //o is deep frozen
Warning: I assume your object's properties are enumerable, if not then use getOwnPropertyNames
instead.
Upvotes: 1
Reputation: 4643
Checkout deep-freeze
it does recursively Object.freeze()
on objects
Here's how they implement it
function deepFreeze (o) {
Object.freeze(o);
Object.getOwnPropertyNames(o).forEach(function (prop) {
if (o.hasOwnProperty(prop)
&& o[prop] !== null
&& (typeof o[prop] === "object" || typeof o[prop] === "function")
&& !Object.isFrozen(o[prop])) {
deepFreeze(o[prop]);
}
});
return o;
};
Upvotes: 1