【问题标题】:Immutable types in javascriptjavascript中的不可变类型
【发布时间】:2011-12-07 06:18:55
【问题描述】:

在我看来,不可变类型在 Javascript 中是不可能的,或者有人知道创建它们的任何技巧吗?这是一个好习惯还是坏习惯?

比如,

var Point2D = function Point2D(x, y) {

    var _x = x;
    var _y = y;

    function constructor() {

        var self = {};

        // Pseudo-Immutable concept
        self.x = function() {
            return _x;
        }();

        self.y = function() {
            return _y;
        }();

        return self;
    }

    return constructor();

}

这当然不是真正不可变的,但如果它是 1) 有据可查的属性 'x' 和 'y' 是 getter 函数或 2) 在验证不变性时引发某种警报,那么它可以充当事实上的不可变对象。

想法?

【问题讨论】:

    标签: javascript oop types immutability


    【解决方案1】:

    如果您不必担心旧版浏览器,您可以查看Object.defineProperty

    除此之外,我认为没有太多选择,因为对象上的任何函数/属性都可以在 JavaScript 中的任何时候重新定义。

    【讨论】:

    • 值得一提的是writeable默认是false,所以创建不可变属性就像写:Object.defineProperty(obj, 'x', {value: 5});一样简单。
    • @SeanThoman 这并不正确。从 ECMAScript 5.1 开始,Object 上有 freeze 函数,它使对象不可变。请参阅下面的答案,了解在 JS 中实现不变性的一些不同方法。
    【解决方案2】:

    可以在 javascript 中定义属性 getter 和 setter:

    function Point2D(){
        this.__defineGetter__("x", function(){
    
        });
    
        this.__defineSetter__("x", function(val){
    
        });
    }
    

    但是,他们修改的基础变量将是可变的。

    【讨论】:

    • 不过,这不在标准中。最好使用Object.defineProperty
    【解决方案3】:
    Point2D = function(x, y)
    {
        var privateX = x;
        var privateY = y;
    
        return {
            getX: function()
            {
                return privateX;
            },
            getY: function()
            {
                return privateY;
            }
        };
    };
    
    var foo = new Point2D(20, 30);
    console.log(foo.getX(), foo.getY());
    

    由于privateX和privateY只存在于构造函数的范围内,所以只能被构造函数中定义的函数(getX,getY)访问。

    【讨论】:

    • getX 和 getY 仍然不是真正不可变的,您可以将它们重新分配给基本上任何东西
    • 对,但是数据是不可变的。修改 getter 只会使其无法访问。 :-)
    • 数据也不是真正不可变的,尽管它更接近。这更像是一个私有变量。特别是考虑到 privateX 和 privateY 仍然可以在内部更改。
    • 有了这段代码,它在所有实际用途中都是不可变的。是的,可以将代码私下添加到类中以更改数据,但此时它不再是同一件事。这种方法的主要问题是,如果您要创建大量实例,它会相对昂贵。
    【解决方案4】:

    现在有一个新的 javascript 保留字 constconst 使运行时常量不可变。

    注意:const 不会将对象或数组呈现为不可变的。

    您可以通过使用Object.freeze 来防止对象的突变,从而有点不可变。

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const http://caniuse.com/#search=const

    DEMO

    【讨论】:

    • 这对属性没有用处。此外,它不在标准中(还)。
    • OP 询问了不可变类型,而不是具体的不可变属性。它所有主流浏览器都可用的标准kangax.github.io/es5-compat-table/es6/#const
    • 它在属性中也有一些用途,因为_x 可以是一个常量(查看 OP 的示例)
    • 这绝对值得一提。
    • 不,这令人困惑 const 和不变性。 const 数组(或任何对象)仍然是可变的。
    【解决方案5】:

    您可以使用Object.freeze(o); 使对象在新浏览器中不可变。

    Point2D 可以这样实现:

    var Point2D = function(x, y) { 
        this.x = x; 
        this.y = y;
        Object.freeze(this);
    }  
    

    现在无法向Point2D 对象添加新属性,并且无法更改其现有属性:

    > var p = new Point2D(99, 123)
    undefined
    > p.x
    99
    > p.x = 7
    7
    > p.x
    99
    > p.foo = "This won't be added"
    undefined
    > JSON.stringify(p);
    "{"x":99,"y":123}"
    

    如果您只想锁定对象以不添加任何新属性,则可以改用Object.seal(o);。这将允许您更改现有属性,但不能添加新属性。

    > var o = {x:1, y:2}
    undefined
    > Object.seal(o);
    [object Object]
    > JSON.stringify(o);
    "{"x":1,"y":2}"
    > o.foo = "This won't be added";
    99
    > o.x = 37 // Seal allows to mutate object 
    37
    JSON.stringify(o);
    "{"x":37,"y":2}"
    

    freezeseal 是 ECMAScript 5.1 的一部分 described more formally here

    MDN 声明freeze 支持:

    • Firefox (Gecko) 4 (2.0)
    • 铬 6
    • Internet Explorer 9
    • 歌剧 12
    • Safari 5.1

    或者,您可以使用更实用的编码风格:

    var Point2D = function(x, y) { 
       return function(prop) { 
          switch (prop) {
             case "x": return x;
             case "y": return y;
             default: throw new Error("Property '" + prop + "' not supported");
          }
       };
    }
    

    用法如下:

    > var p = Point2D(1,2)
    undefined
    > p("x")
    1
    > p("y")
    2
    > p("foo")
    Error: Property 'foo' not supported
    

    我知道无法使用这种方法更改“属性”xy,因为它们受 Point2D 函数的范围约束。这种方法在javascript中并不常见(据我所知),但类似于在例如Scheme中如何实现消息传递/OO。

    【讨论】:

    • 正如@Fresheyeball 和@Sulthan 指出的那样,子数组和对象在默认情况下不是不可变的,仅使用Object.freeze(this),所以当我在这里进行快速测试时,有必要冻结所有子结构,但我认为一个好的做法是为每个结构定义构造函数。因此,如果要在 Point2D 中进行更改以了解该值以什么单位进行描述,则新结构将是 {x: {value:99, unit:'cm'}},但 Point2D 中的 x 将收到负责转向的 valueWithUnit(99, 'cm')它是不可变的。
    【解决方案6】:

    混合使用一些 ES2015 会有所帮助:

    function Point2D(x, y) {
      const instanceX = x,
        instanceY = y;
    
      return {
        get x() { return instanceX; },
        get y() { return instanceY; }
      }
    }
    

    【讨论】:

    • 在尝试使用代码 sn-p 回答其他人时,您可能需要显示一些结果以让他们确认。否则,您应该使用代码块。
    猜你喜欢
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 2012-02-28
    相关资源
    最近更新 更多