有没有一种简单的方法在Javascript中完全冻结一个对象及其子元素(Deep Freeze)?

3

通常您需要将对象作为参数传递。访问这些对象的函数是按引用传递的,可以更改原始对象。这取决于情况,可能是不希望的结果。那么有没有一种方法可以冻结对象?我知道有 Object.freeze()

但它不会影响其中的对象/数组。

例如

Original Answer翻译成"最初的回答"

a = { arr: [1,2,3]}; 
Object.freeze(a);
a.arr.push(4); // still works

你有尝试实现它(或找到实现方式)吗?发生了什么事情? - jonrsharpe
我尝试了一些关于getter和setter的东西,但它不具有可扩展性。 - Muhammad Umer
7个回答

2

要将所有可枚举属性冻结(ES2015+):

// Recursively freeze an object
const deepFreeze = x => {
  Object.freeze(x)
  Object.values(x).forEach(deepFreeze)
}

如果您有循环引用:
// Recursively freeze an object with circular references
const deepFreeze = x => {
  Object.freeze(x)
  Object.values(x).filter(x => !Object.isFrozen(x)).forEach(deepFreeze)
}

如果您需要将浅冻的物品深度冷冻(速度稍慢),请执行以下操作:
// 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))
}

简述:两行概括。
const deepFreeze = (x, frozen = []) => frozen.includes(x) ||
  frozen.push(Object.freeze(x)) && Object.values(x).forEach(x => deepFreeze(x, frozen))

1
如果您查看MDN,那里有一个函数建议使用deepFreeze功能,但它不是堆栈安全的。我个人有一个ES5版本用于异步迭代。对于ES6,以下代码可能适用,尽管我没有进行彻底的测试:
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

警告:我假设你的对象属性是可枚举的,如果不是,请使用getOwnPropertyNames代替。


1
function deepFreeze(object) {

  // Retrieve the property names defined on object
  var propNames = Object.getOwnPropertyNames(object);

  // Freeze properties before freezing self

  for (let name of propNames) {
    let value = object[name];

    object[name] = value && typeof value === "object" ? 
      deepFreeze(value) : 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

(第二段代码[第二个灰色区域])https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze

谢谢 :)


1
你可以这样制作一个非常简单的递归解决方案:

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


0

另一种方法是使用代理。而不是深度冻结对象,您会得到一个代理对象,禁止写入:

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

0

结账 deep-freeze 它会对对象递归执行 Object.freeze()

这是他们的实现方式

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

2
不,不要使用这个,这是Node.js生态系统的所有问题的例子。整个软件包甚至带有一个测试,只是为了保存你从编写带有两个条件的最简单递归循环中解放出来。 - Thomas Brierley
我同意。我尽量减少使用外部包。 - Muhammad Umer
但为什么要重新发明轮子呢?如果已经有了现成的东西,就在其基础上进行开发。 - Rami Jarrar
我的意思是我会复制粘贴这个代码,但不会受到随时可能更改的外部依赖的限制。 - Muhammad Umer

0
你可能想考虑使用immer。它是一个创建不可变Js数据结构的优秀工具。 在你的情况下,代码如下:
const a = produce({ arr: [1,2,3] }, () => {})

() => {} 是必要的,因为 produce 需要一个能够改变第一个参数的函数,但这里并不需要进行任何改变。

如果你只是想冻结一些数据结构,并且不会在其他地方使用不可变性,那么使用额外的第三方库可能并不值得。上述解决方案已经足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接