为什么我需要冻结JavaScript中的对象?

时间:2013-02-09 20:36:00

标签: javascript immutability

当有人需要在JavaScript中使用Object.freeze时,我不清楚。 MDN和MSDN在有用时不提供真实的例子。 我得到的部分是关于无法在运行时更改它,由崩溃强制执行。问题是我什么时候才能体会到崩溃。

对我而言,不变性是一种设计时限制,应该由类型检查器保证。

在动态类型语言中发生运行时崩溃是否有任何意义,除了比以后更好地检测违规之外?

11 个答案:

答案 0 :(得分:13)

Object.freeze函数执行以下操作:

  • 使对象不可扩展,因此无法向其添加新属性。
  • 为对象的所有属性设置可配置属性为false。如果--configurable为false,则无法更改属性属性,也无法删除该属性。
  • 为对象的所有数据属性设置writable属性为false。当writable为false时,无法更改data属性值。

那是什么部分,但为什么会有人这样做吗?

嗯,在面向对象的范例中,存在这样的概念:现有的API包含某些不打算在其当前上下文之外进行扩展,修改或重用的元素。各种语言中的final关键字是最合适的。即使在未编译且因此易于修改的语言中,它仍然存在,即PHP,在这种情况下是JavaScript。

答案 1 :(得分:10)

如果对象表示逻辑上不可变的数据结构,则可以使用此方法,尤其是在:

  • 更改对象的属性或更改其“鸭子类型”可能会导致应用程序中其他位置出现错误行为
  • 该对象类似于可变类型或者看起来是可变的,并且您希望程序员在尝试更改它时发出警告而不是获取未定义的行为。

作为API作者,这可能正是您想要的行为。例如,您可能有一个内部缓存的结构,该结构表示您通过引用提供给API用户的规范服务器响应,但仍然在内部用于各种目的。您的用户可以引用此结构,但更改它可能会导致您的API具有未定义的行为。在这种情况下,如果用户尝试修改异常,则需要抛出异常。

答案 2 :(得分:5)

在我的nodejs服务器环境中,我使用冻结的原因与使用严格'使用严格的#39;相同。如果我有一个我不想被扩展或修改的对象,我会冻结它。如果某些东西试图扩展或修改我的冻结对象,我希望我的应用程序抛出错误。

对我而言,这与一致,高质量,更安全的代码有关。

另外, Chrome is showing significant performance increases working with frozen objects.

编辑: 在我最近的项目中,我在政府实体之间发送/接收加密数据。有很多配置值。我正在使用冻结对象来获取这些值。修改这些值可能会产生严重的不良副作用。另外,正如我之前链接的那样,Chrome显示了冻结对象的性能优势,我假设nodejs也是如此。

为简单起见,一个例子是:

var US_COIN_VALUE = {
        QUARTER: 25,
        DIME: 10,
        NICKEL: 5,
        PENNY: 1
    };

return Object.freeze( US_COIN_VALUE );

没有理由修改此示例中的值。并享受速度优化带来的好处。

答案 3 :(得分:4)

Object.freeze 主要用于功能编程(Immutability)

不变性是函数式编程的核心概念,因为没有它,程序中的数据流是有损的。状态历史被抛弃,奇怪的错误可能会蔓延到您的软件中。

在JavaScript中,重要的是不要将const与不可变性混淆。 const创建一个变量名绑定,在创建后无法重新分配。 const不会创建不可变对象。您不能更改绑定引用的对象,但您仍然可以更改对象的属性,这意味着使用const创建的绑定是可变的,而不是不可变的。 根本不可更改不可变对象。您可以通过深度冻结对象来使值真正不可变。 JavaScript有一种方法冻结一个深层次的对象

bus_above60_btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        sameFunction();
                    }
                });

bus_conductor_above60 = findViewById(R.id.bus_conductor_above60);

bus_conductor_above60 .setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    if (actionId == EditorInfo.IME_ACTION_DONE) {
                        sameFunction();
                    }
                    return false;
                }
            });

答案 4 :(得分:1)

这是一个老问题,但我认为我有一个很好的案例,冻结可能有所帮助。我今天遇到了这个问题。

问题

class Node {
    constructor() {
        this._children = [];
        this._parent = undefined;
    }

    get children() { return this._children; }
    get parent() { return this._parent; }

    set parent(newParent) {
        // 1. if _parent is not undefined, remove this node from _parent's children
        // 2. set _parent to newParent
        // 3. if newParent is not undefined, add this node to newParent's children
    }

    addChild(node) { node.parent = this; }
    removeChild(node) { node.parent === this && (node.parent = undefined); }
    ...
}

如您所见,当您更改父节点时,它会自动处理这些节点之间的连接,从而使子节点和父节点保持同步。但是,这里有一个问题:

let newNode = new Node();
myNode.children.push(newNode);

现在,myNodenewNode中有children,但newNode没有myNode作为其parent。所以你刚刚打破了它。

(OFF-TOPIC)你为什么要揭露这些孩子?

是的,我可以创建许多方法:countChildren(),getChild(index),getChildrenIterator()(返回生成器),findChildIndex(节点)等等......但它真的是一种更好的方法而不只是返回一个数组,它提供了所有javascript程序员已经知道的接口?

  1. 您可以访问其length以查看它有多少孩子;
  2. 您可以按索引访问孩子(例如children[i]);
  3. 您可以使用for .. of;
  4. 对其进行迭代
  5. 您可以使用Array提供的其他一些不错的方法。
  6. 注意:返回一个数组副本是不可能的!它需要线性时间,并且对原始数组的任何更新都不会传播到副本!

    解决方案

        get children() { return Object.freeze(Object.create(this._children)); }
    
        // OR, if you deeply care about performance:
        get children() {
            return this._PUBLIC_children === undefined
                ? (this._PUBLIC_children = Object.freeze(Object.create(this._children)))
                : this._PUBLIC_children;
        }
    

    完成!

    1. Object.create:我们创建了一个继承自this._children的对象(即this._children__proto__)。仅这一点就解决了几乎所有问题:
      • 简单快速(恒定时间)
      • 您可以使用Array接口提供的任何内容
      • 如果修改了返回的对象,则不会更改原始对象!
    2. Object.freeze:但是,您可以修改返回的对象而这些更改不会影响原始数组,这对于该类用户来说非常困惑!所以,我们只是冻结它。如果他试图修改它,则会抛出异常(假设严格模式)并且他知道他不能(以及为什么)。如果您没有处于严格模式,myFrozenObject[x] = y会引发遗憾,但myFrozenObject未被修改,所以它仍然不是那么奇怪。
    3. 当然,程序员可以通过访问__proto__来绕过它,例如:

      someNode.children.__proto__.push(new Node());
      

      但我喜欢认为在这种情况下,他们实际上知道他们在做什么,并且有充分的理由这样做。

      重要:请注意,这对于对象来说效果不佳:在for ...中使用hasOwnProperty将始终返回false。

      更新:使用Proxy解决对象的同一问题

      只是为了完成:如果你有一个对象而不是一个数组,你仍然可以通过使用代理来解决这个问题。实际上,这是一个通用的解决方案,应该适用于任何类型的元素,但我建议反对(如果你可以避免它)由于性能问题:

          get myObject() { return Object.freeze(new Proxy(this._myObject, {})); }
      

      这仍然会返回一个无法更改的对象,但保留了它的所有只读功能。如果你真的需要,你可以删除Object.freeze并在代理中实现所需的陷阱(set,deleteProperty,...),但这需要额外的努力,这就是{{1}的原因代理人派上用场了。

答案 5 :(得分:0)

当您使用交互式工具时,我可以看到这很有用。而不是:

if ( ! obj.isFrozen() ) {
    obj.x = mouse[0];
    obj.y = mouse[1];
}

你可以这样做:

obj.x = mouse[0];
obj.y = mouse[1];

属性仅在对象未冻结时才会更新。

答案 6 :(得分:0)

我可以想到Object.freeze会派上几个非常方便的地方。

可以使用freeze的第一个真实世界实现是在开发一个需要服务器上的“状态”以匹配浏览器中的内容的应用程序时。例如,假设您需要为函数调用添加一定级别的权限。如果您正在使用某个应用程序,那么开发人员可能会在没有意识到的情况下轻松更改或覆盖权限设置(特别是如果对象是通过引用传递的话!)。但是,大体上的权限永远不会改变,并且当它们被更改时是错误的。因此,在这种情况下,可以冻结权限对象,从而限制开发人员错误地错误地“设置”权限。对于类似用户的数据,例如登录名或电子邮件地址,也可以这样说。使用错误的代码可能会错误地或恶意地破坏这些东西。

另一种典型的解决方案是游戏循环代码。游戏状态有很多设置,您希望冻结以保持游戏状态与服务器保持同步。

将Object.freeze视为将对象设为常量的方法。任何时候你想要变量常量,你可以有一个对象常量冻结出于类似的原因。

有时您希望通过函数和数据传递传递不可变对象,并且只允许使用setter更新原始对象。这可以通过克隆和冻结“getters”对象并仅使用“setter”更新原始文件来完成。

这些都不是有效的东西吗?也可以说,由于缺乏动态变量,冻结的物体可能更具性能,但我还没有看到任何证据。

答案 7 :(得分:0)

Object.freeze的唯一实际用途是在开发过程中。对于生产代码,冷冻/密封物体绝对没有任何好处。

愚蠢的错别字

它可以帮助您在开发过程中发现这个非常常见的问题:

if (myObject.someProp = 5) {
    doSomething();
}

在严格模式下,如果冻结myObject,则会抛出错误。

强制执行编码协议/限制

它还有助于在团队中执行某个协议,特别是对于可能与其他人不具有相同编码风格的新成员。

很多Java人都喜欢在对象中添加很多方法,让JS感觉更熟悉。冻结物体会阻止他们这样做。

答案 8 :(得分:0)

当您在JS中编写库/框架时,您不希望某些开发人员通过重新分配"内部"来打破您的动态语言创建。或公共财产。 这是不可变性的最明显的用例。

答案 9 :(得分:0)

不知道这是否有帮助,但我用它来创建简单的枚举。它允许我希望不会在数据库中获取duff数据,因为我们知道数据源已被尝试不可更改而无需故意破坏代码。从静态类型的角度来看,它允许对代码构造进行推理。

答案 10 :(得分:0)

使用V8 release v7.6可以大大提高冻结/密封阵列的性能。因此,您希望冻结对象的原因之一是代码对性能至关重要。