最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

arrays - Is there an easy way to completely freeze object and its children in Javascript, (Deep Freeze)? - Stack Overflow

programmeradmin0浏览0评论

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 oute. 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

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 oute. 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
Share Improve this question edited May 31, 2019 at 22:53 jonrsharpe 122k30 gold badges267 silver badges474 bronze badges asked May 31, 2019 at 22:22 Muhammad UmerMuhammad Umer 18.2k24 gold badges109 silver badges174 bronze badges 2
  • Have you tried to implement that (or find an implementation)? What happened? – jonrsharpe Commented May 31, 2019 at 22:24
  • I tried something with getter and setter but it doesn't scale – Muhammad Umer Commented May 31, 2019 at 22:26
Add a ment  | 

8 Answers 8

Reset to default 2

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))

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 :)

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);

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;
};

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.

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.

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`)
    }
  })
}

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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论