不可设置和不可写属性仍然是可变的

Unsettable & Unwritable properties are still mutable

本文关键字:仍然是 设置 属性      更新时间:2023-09-26

我正在尝试在构造函数中创建一个属性,该属性是不可变的,除非通过原型函数。我正在尝试关闭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原型的所有复杂性都不是很了解。

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

MDN 文档Object.freeze()有一个示例,展示了如何递归冻结("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异常,指示该属性不可写;

类型错误:无法定义数组末尾以上的数组索引属性 具有不可写长度

Safari 检查器还会返回一个TypeError异常:

类型错误: 试图分配给只读属性。

在闭包的帮助下,您在很大程度上可以完成此操作。 这就是你在 JavaScript 中实现隐私的方式。

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

在我的示例中,foo 函数包含一个 _foo 变量,该变量只能由函数 foo 返回的对象中的方法设置。您正在有效地为函数 foo 范围内的 var 创建一个 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());