难以置信的&不可写属性仍然可变

时间:2016-02-05 00:24:04

标签: javascript oop immutability

我试图在构造函数中创建一个属性,除了通过原型函数之外,它是不可变的。我正在尝试关闭此MDN文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties。但似乎没有办法让财产完全不变。考虑一个简单的例子:

function Test(){
    Object.defineProperties(this,{
        elems : { value : [] }
    })
}
Test.prototype.addElem = function(newElem){
    if (this.elems.indexOf(newElem) == -1){
        this.elems.push(newElem);
    }
};

在大多数情况下都可以正常工作(不可分配):

>a = new Test()
Object { , 1 more… }
>a.elems
Array [  ]
>a.elems = 10
10
>a.elems
Array [  ]

不幸的是,它仍然是可变的。考虑:

>a.elems.push(10)
1
>a.elems
Array [ 10 ]

我确信它们是其他函数(数组或对象方法?),它们会改变不可写的&的值。不可设置的财产。推是我遇到的那个。有没有办法实现这个目标?我知道一个可能的解决方案是:

function Test(){
    var elems = [];
    this.addElem = function(newElem){
        if (elems.indexOf(newElem) == -1){
            elems.push(newElem);
        }
    }
}

但是我已经读到这是内存效率低下的问题,特别是当有很多"类"时。另外,我正在研究的可能有很多这样的方法,所以我更担心内存的考虑。

有什么想法吗?我对JS原型的所有复杂性并不是很了解。

2 个答案:

答案 0 :(得分:1)

在JavaScript中,默认情况下对象是可扩展的,但如果您能够利用ES5,则应该能够使用Object.seal()Object.freeze()方法来获取不可变属性。

Object.freeze()的MDN文档有一个示例,说明如何递归冻结(“deepFreeze”)对象的所有属性,从而有效地使其完全不可变。

这是一个概念验证,它将问题中的代码与文档中的代码结合起来:

function Test() {
    Object.defineProperties(this, {
        elems : { value : [] }
    })
}

Test.prototype.addElem = function(newElem) {
    if (this.elems.indexOf(newElem) == -1) {
        this.elems.push(newElem);
    }
};

function deepFreeze(obj) {

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

  // Freeze properties before freezing self
  propNames.forEach(function(name) {
    var prop = obj[name];

    // Freeze prop if it is an object
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // Freeze self (no-op if already frozen)
  return Object.freeze(obj);
}

a = new Test();

a.elems.push(1);
console.log(a.elems); // [1]

deepFreeze(a);

a.elems.push(2);

console.log(a.elems); // Error

在FireBug中,对象“深度冻结”后的a.elems.push()返回TypeError异常,表示该属性不可写;

  

TypeError:无法在数组末尾定义数组索引属性   具有不可写的长度

Safari检查器还会返回TypeError例外:

  

TypeError:尝试分配给readonly属性。

答案 1 :(得分:0)

你可以在一个闭包的帮助下完成这个。这就是您在JavaScript中实现隐私的方式。

简而言之,您在函数内部创建一个变量,并让该函数返回一个包含setter / getters的对象。

在我的例子中,foo函数包含一个_foo变量,该变量只能由函数foo返回的对象中的方法设置。您正在使用函数foo的范围有效地创建一个API。

var foo = function(config){

    if (!config) {
        config = {};
    }
//enclosed variable
    var _foo = {
        bar: []
    };

    if (config.bar) {//All item to be initialized with data
        _foo.bar = config.bar;
    }

    var fooAPI = {
        addBarItem: function(val){
            _foo.bar.push(val);
            return _foo.bar.length - 1;//return idenx of item added
        },
        removeBarItem: function(index) {
            return _foo.bar.slice(index, 1);//return the removed item
        },
        getBarItem: function(index) {
            return _foo.bar[index];//return the removed item
        },
        emptyBarItems: function() {
            return _foo.bar.slice(0, _foo.bar.length);//return all removed
        },
    getBarItems: function(){
        //clone bar do not return reference to it in order to keep private
        var newBar = [];
         _foo.bar.forEach(function(item){
             newBar.push(item);
            });
            return newBar;
        }
    };

    return fooAPI;
};

var myFoo = new foo({bar: ['alpha', 'beta', 'gamma']});
console.log(myFoo.getBarItems());