JavaScript中Object.freeze或Object.seal的相反操作是什么?

89

Object.freezeObject.seal的反义词是什么?是否有名为“detach”之类的函数?


游戏Return True to Win有三个问题涉及到解冻冰冻的物体。但是我还没有找到任何答案!(请看这里的“解冻”问题:https://github.com/Rafalsky/return-true-to-win,但也有剧透)。 - joeytwiddle
13个回答

129

一旦对象被冻结,就没有办法解冻它。这是一种终极的锁定形式,冻结后的对象无法被修改。这是确保您的对象永久保持不变的最佳方式。

来源

冻结对象是最终的锁定形式。一旦对象被冻结,就不能解冻,也不能以任何方式篡改。这是确保您的对象永远保持原样的最佳方式。


13
就现有对象的变异而言,这个技术上是正确的。然而,你现在可以复制/克隆现有对象并改变其属性。参见Andennour TOUMI的答案(https://dev59.com/WWIk5IYBdhLWcg3wbdnx#26752410)以了解如何实现此操作。 - Levi Roberts
10
如果解冻调用发生在同一作用域下,对象就可以被解冻,这将极大地节省内存使用,避免复制。例如:Object.freeze(obj); thirdPartyRoutine(obj); Object.unfreeze(obj); /*继续使用obj*/。如果能实现这一点,那将是非常棒的。 - trusktr
6
可能是为了安全考虑?如果你有一个全局对象,不希望任何人碰它。如果潜在的黑客可以使用Object.unfreeze(obj)绕过冻结,那么冻结就没有意义了。 - Jaden Baptista
1
如果存在这种情况,不可避免地,您的同事会在您指望被冻结的对象上使用它。这些问题通常也会逃脱单元测试的检测。 - Eric Haynes
5
对于在同一作用域中的冻结和解冻,让 freeze(锁定)函数返回一个 key 以便后续 unfreeze(解锁)操作是有意义的。 - Ghost4Man
显示剩余2条评论

20

我认为你可以使用一些技巧:

  • 首先创建一个原始对象的副本临时变量
  • 然后将原始变量设置为未定义
  • 再从临时变量中重新设置它的值。

代码如下:

var obj = {a : 5};

console.log(obj); // {a: 5}
Object.freeze(obj);

obj.b = 10; // trying to add something to obj var
console.log(obj); // output: {a: 5} -> means its frozen

// Now use this trick
var tempObj = {};
for(var i in obj){
    tempObj[i] = obj[i];
}
console.log(tempObj); // {a: 5}

// Resetting obj var
obj = tempObj;
console.log(obj);// {a: 5}

obj.b = 10; // trying to add something to obj var
console.log(obj); // output: {a: 5, b: 10} -> means it's not frozen anymore
注意:记住一件事,不要做tempObj = obj,因为那样它不会工作,因为tempObj也被冻结在那里。
这里有个例子:http://jsfiddle.net/mpSYu/

5
我理解你的意思。复制数值是一种"绕过"冻结对象的方法,但最终obj != tempObj - 他们的签名不再相同。 - CodingIntrigue
但是,冻结的对象可以被删除!因此,这现在可以是一个可接受的解决方案。克隆冻结的对象并将其分配给相同的引用,然后删除冻结的对象将使我们回到旧状态,唯一的缺点是手动克隆它。当然,我们失去了原始对象。 - vivek_nk
4
不是一样的。仅因新变量名称相同并不意味着它是相同的引用(指针地址)。您覆盖了冻结的“obj”,但仅在局部范围内。顺便说一下,我个人喜欢对象无法被解冻。 - Alexander Tsepkov
这种做法的缺点是会分配更多的内存,如果在动画的每一帧中都发生这种情况,那么它就可能变得“卡顿”。如果对象在相同范围内进行解冻调用,则效果会非常好。或者,Object.freeze可以返回一个“unfreeze”函数,可以小心地传递(就像Promise resolve和reject函数可以小心地传递一样)。 - trusktr
这不是解冻,而只是将原始对象复制到tempObj,并重新分配obj变量给tempObj。换句话说,原始的obj现在指向新的引用。 证明是,更改obj后,obj和tempObj记录的内容相同。您可以更简单地实现此操作,而无需使用for循环,只需使用var tempObj = {...obj}; obj = tempObj; - Dalibor
如果obj被声明为const会怎样? - Valentino Miori

14

无法解冻已经被冻结的对象。

但是,您可以通过覆盖 Object.freeze 方法使讨厌的库无法在将来冻结任何内容:

Object.freeze = function(obj) { return obj; }; // just return the original object

在大多数情况下这已经足够了。只需在库加载之前运行上面的代码,它就无法再冻结任何东西了。;)


编辑:一些库(例如immer@7.0.7)存在问题,除非在调用Object.freeze(obj)Object.isFrozen(obj)返回true

以下是一个更安全的防止对象冻结的方法:(原始检查是因为如果传递原始值,WeakSet会出错)

const fakeFrozenObjects = new WeakSet();
function isPrimitive(val) {
    return val == null || (typeof val != "object" && typeof val != "function");
}
Object.freeze = obj=>{
    if (!isPrimitive(obj)) fakeFrozenObjects.add(obj);
    return obj;
};
Object.isFrozen = obj=>{
    if (isPrimitive(obj)) return true;
    return fakeFrozenObjects.has(obj);
};

1
这对我来说似乎很合理,就像一个反向的垫片。为什么会被踩呢? - amay0048
20
一块垫片可以增加行为。而这种方式会删除已有的行为,这是一个糟糕的想法,不应该在生产中使用。Object.freeze 的工作方式有其原因。 - CodingIntrigue
3
在生产环境中使用删除功能的垫片是一个糟糕的想法。另一方面,如果您想进行黑客攻击,为了实现您的目标,可以做任何事情。 - mjz19910
2
您可以通过检查要解冻的对象的特征来扩展函数覆盖,以便它在第一次被冻结时就不会被冻结。其他对象仍然可以被冻结。 - Mike de Klerk
1
这与原帖没有太多关系。按照库的接口建议处理“麻烦”的库可能更好。我愿意相信东西被冻结都是有原因的。这很可能会引起更多问题而不是解决问题。你可以删除自己的答案...;-) - theUtherSide
1
这实际上对我在单元测试中的帮助很大,因为一些hacky的东西是非常受欢迎的... - Kristonitas

14

有线解决方案 :)

 Object.unfreeze=function(o){
       var oo=undefined;
        if( o instanceof Array){
                oo=[];var clone=function(v){oo.push(v)};
                o.forEach(clone); 
        }else if(o instanceof String){
           oo=new String(o).toString();
      }else  if(typeof o =='object'){

         oo={};
        for (var property in o){oo[property] = o[property];}


        }
        return oo;
 }

最佳实践:


 var obj={a:1,b:2}
 // {a:1,b:2}
   obj.c=3; 
  //{a:1,b:2,c:3}
  Object.freeze(obj)
  //{a:1,b:2,c:3}
  obj.d=5;
  //Error: Read only object 
  obj=Object.unfreeze(obj)
  //{a:1,b:2,c:3}
   obj.d=5;
  //{a:1,b:2,c:3,d:5}

 var tab=[1,2,3]
 //[1,2,3]
tab.push(4)
 //[1,2,3,4]
Object.freeze(tab);
//[1,2,3,4]
tab.push(5)
// Error : Ready only object
tab=Object.unfreeze(tab);
//[1,2,3,4]
tab.push(9)

//[1,2,3,4,9]

2
这应该是被接受的答案。虽然其他答案在技术上是正确的,但这个回答完成了OP的任务。 - Levi Roberts
53
这并不是解除冻结,而是复制对象,完全是两回事。 - Willem D'Haeseleer
5
记录一下,对于这个问题的所有答案都是错误的,并涉及一个比仍然被冻结的对象更高层次的新副本。 - Leathan
1
如果这是对象的副本,那么展开运算符或Object.assign是更好的选择。然而,复制/克隆也不是我要寻找的。 - ghiscoding
1
永远不要修改内置类或原型。这是一种可怕的反模式。 - jonschlinkert
显示剩余3条评论

5

您无法解冻(thaw)一个对象,但是如果对象只是由原始数据类型组成的集合(没有函数或类),您可以通过以下方式获得一个解冻的克隆对象:

const unfrozenObj = JSON.parse(JSON.stringify(frozenObj));

3
我想知道你是否可以将此视为解冻。实质上,你正在创建一个新对象,冻结对象仍然是不可变的。 - Deepak Jha

2

根据回答和评论,似乎人们在需要改变冻结对象时会找到这篇文章。虽然说“这是不可能的”是准确的,但并不是很有帮助。

考虑到这一点,这里是我基于@Abdennour TOUMI 2014年的答案而提出的现代变体。

function unfreeze(obj) {
  if (Object.isFrozen(obj)) {
    return Object.assign({}, obj);
  }
  return obj;
}

以下是如何使用它的示例:
let myObj = { one: "1", two: "2", three: "3" };
Object.freeze(myObj);
// error
// myObj.one = "4";
// succeeds
myObj = unfreeze(myObj);
myObj.one = "4";


这并不会解冻对象。它只是创建了一个副本,就像其他所有答案一样。而且,说其他评论“准确但无用”是完全没有意义的。如果有人想让1+1=3,并且评论说这是不可能的,你也会说“准确但无用”吗?这就是不明智的。你不能解冻对象,也不能将1加到自身以得到3。故事结束。唯一真正有用的事情是帮助别人以不涉及解冻对象(或更改算术)的方式解决他们的实际问题,而这个答案并没有做到。 - siride
1
@siride 我的意思是,27个人都给出同样的答案,即1+1=3不可能成立,这既准确又没有帮助。对现有答案进行评论或点赞应该就足够了。 - Triforcey

0
一个非常简单且非常快速的地图(因为克隆和冻结需要时间)。您可以扩展它并从常规js对象或其他方法中添加深度转换和合并。
function IMap(key, value) {
  let frozen = false;

  this.lock = function lock() {
    frozen = true;
    return this;
  };

  this.unlock = function unlock(password) {
    if (!password) {
      throw Error("Empty IMap key.");
    }
    if (password === key) {
      frozen = false;
    } else {
      throw Error(`Wrong IMap unlock key.`);
    }
    return this;
  };

  this.set = function set(prop, val) {
    if (!frozen) {
      value[prop] = val;
    } else {
      throw TypeError("IMap is locked.");
    }
    return this;
  };

  this.get = function get(prop) {
    return value[prop];
  };
}

export function iMap(password, source) {
  return new IMap(password, source);
}

用法:

const item = iMap('your-password',{ something: 'value' });
item.lock();
item.set('something',5); // throws an error
item.unlock('your-password').set('something',5).lock(); // ok

0

我也遇到了这个问题。为了解决它,我使用了JavaScript JSON API来解冻我的对象:const unfreezeObject = JSON.parse(JSON.stringify(freezeObject))。之后,我进行了所有必要的变异。


手动复制(首选)或使用lodash的deepClone是更好的选择。 JSON.parse(JSON.stringify(...))是执行深度复制的有缺陷的方法。此外,除非您进行了深度冻结(递归应用冻结),否则浅层复制{...item}可能就足够了。 - TamusJRoyce

0

我认为,如果您想保留原始对象,但仍然具有未冻结的行为,则唯一剩下的逻辑是创建一个具有原始对象相同行为的子类,然后在子类内部,通过创建可变访问器来覆盖现有对象的行为,从而覆盖您想要更改的访问器。

这样,如果您仍然希望出于某种奇怪的原因保留原始变量/对象,或者保持系统的其余部分一致,那么您可以将其保持不变,同时使用直接继承自原始对象的子类版本,以获得其现有行为的好处,而无需重新制作原始父对象。我看不出试图做不可能或不合逻辑的事情似乎是如此令人向往的事情。

除非您创建子类,否则您只是在做不可能的事情。


你所提供的答案目前并不清晰,请进行[编辑]以添加更多细节,以帮助其他人理解它是如何回答所提出的问题。您可以在帮助中心找到有关编写良好答案的更多信息。 - Community

0
有点晚了,但你也可以在可变变量(let)中创建一个对象,并在需要重置时将原始对象重新分配给变量。
例如:
let obj = { objProp: "example" };

if (condition) {
    Object.freeze(obj);
}
else {
    obj = { objProp: "example" };
}

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