在JS中禁用属性变异

时间:2018-03-22 11:16:40

标签: javascript abstraction

我正在创建一个组件,并试图破坏我的实现。我们的想法是不允许用户操纵暴露的属性。

实施是这样的:

function MyClass(){
  var data = [];
  Object.defineProperty(this, 'data', {
    get: function(){ return data; },
    set: function(){ throw new Error('This operation is not allowed'); },
    configurable: false,
  });
}

var obj = new MyClass();

try {
  obj.data = [];
} catch(ex) {
  console.log('mutation handled');
}

obj.data.push('Found a way to mutate');

console.log(obj.data)

如您所见,设置属性已处理,但用户仍可使用.push对其进行变更。这是因为我正在返回参考文献。

我已经处理过这样的案件:

function MyClass(){
  var data = [];
  Object.defineProperty(this, 'data', {
    get: function(){ return data.slice(); },
    set: function(){ throw new Error('This operation is not allowed'); },
    configurable: false,
  });
}

var obj = new MyClass();

try {
  obj.data = [];
} catch(ex) {
  console.log('mutation handled');
}

obj.data.push('Found a way to mutate');

console.log(obj.data)

如你所见,我正在返回一个新数组来解决这个问题。不确定它将如何影响性能。

问题:是否有另一种方法可以不允许用户改变 object 类型的属性?

我尝试过使用writable: false,但在get使用它时会出错。

注意:我希望这个数组在课堂内是可变的,但不是来自外部。

2 个答案:

答案 0 :(得分:2)

您的问题是您实际上阻止了修改MyClass的尝试。但是,MyClass的其他对象成员仍然是JavaScript对象。这样你就是这样做(每次调用get都返回一个新的数组)是最好的方法之一,当然,这取决于你调用get的频率或数组的长度可能有性能缺陷。

当然,如果您可以使用ES6,则可以扩展本机Array以创建ReadOnlyArray类。实际上,您也可以在ES5中执行此操作,但是您无法使用方括号从数组中的特定索引检索值。

如果可以避免使用Internet Explorer,另一个选择是使用Proxieshttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)。

使用代理,您可以捕获调用以获取对象的属性,并决定返回或执行的操作。

在下面的示例中,我们为数组创建了一个代理。正如您在处理程序中看到的,我们定义了一个get函数。只要访问目标对象的属性值,就会调用此函数。这包括访问索引或方法,因为调用方法基本上是检索属性(函数)的值然后调用它。

如您所见,如果属性是整数,我们将返回数组中的该位置。如果属性是'length',那么我们返回数组的长度。在任何其他情况下,我们返回一个void函数。

这样做的好处是proxyArray仍然表现得像一个数组。您可以使用方括号来获取其索引并使用属性长度。但是,如果你尝试做proxyArray.push(23)之类的事情,那就什么都不会发生。

当然,在最终解决方案中,您可能希望根据哪个来决定做什么  正在调用方法。您可能需要mapfilter等方法才能正常工作。

最后,这种方法的最后一个优点是你保留了对原始数组的引用,因此你仍然可以修改它,并且可以通过代理访问它的值。

var handler = {
    get: function(target, property, receiver) {
      var regexp = /[\d]+/;
      if (regexp.exec(property)) { // indexes:
        return target[property];
      }
      
      if (property === 'length') {
        return target.length;
      }
      
      if (typeof (target[property]) === 'function') {
        // return a function that does nothing:
        return function() {};
      }
    }
};

// this is the original array that we keep private
var array = [1, 2, 3];

// this is the 'visible' array:
var proxyArray = new Proxy(array, handler);

console.log(proxyArray[1]);
console.log(proxyArray.length);


console.log(proxyArray.push(32)); // does nothing
console.log(proxyArray[3]); // undefined


// but if we modify the old array:
array.push(23);
console.log(array);

// the proxy is modified
console.log(proxyArray[3]); // 32

当然,问题在于proxyArray实际上不是一个数组,因此,根据您打算如何使用它,这可能是一个问题。

答案 1 :(得分:0)

据我所知,你想要的东西在JavaScript中是不可行的。您可以期待的最好的方法是尽可能地隐藏用户的数据。最好的方法是使用WeakMap

let privateData = new WeakMap();

class MyClass {

    constructor() {

        privateData.set(this, {
            data: []
        });

    }

    addEntry(entry) {
        privateData.get(this).data.push(entry);
    }

    getData() {
        return privateData.get(this).data.concat();
    }

}

只要你永远不会从模块中导出privateDataexport,或在IIFE等内包装,那么你的MyClass实例就可以访问数据但外部力量不能(除了通过你创建的方法)

var myInstance = new MyClass();
myInstance.getData(); // -> []
myInstance.getData().push(1);
myInstance.getData(); // -> []
myInstance.addEntry(100);
myInstance.getData(); // -> [100]