保护封闭内的对象不被从外部修改

Protect object inside closure from being modified from outside

本文关键字:从外部 修改 对象 保护      更新时间:2023-09-26

我创建这个对象是为了能够在其中存储数据,这具有一定的保护级别。只要新数据通过验证,任何人都可以获得它,任何人也可以设置它。

MF.factory = new function(){
    var data = {
        SITE_URL: document.URL.replace(/(https?:'/'/.+?)'/.*/, '$1'),
        xhrSettings: {
            type: 'GET',
            error: function(){},
            response: function(){},
            max_upload_file_size: 8388608
        }
    }
    this.get = function(key){
        return data[key];
    }
    this.set = {
        xhrSettings: {
            type: function(type){if(type in {POST:0,GET:0,PUT:0}) data.xhrSettings.type = type;},
            error: function(func){if(typeof func === 'function') data.xhrSettings.error = func;},
            response: function(func){if(typeof func === 'function') data.xhrSettings.response = func;},
            max_upload_file_size: function(size){if(!isNaN(size)) data.xhrSettings.max_upload_file_size = size;}
        }
    }
};

例如,现在我可以获得SITE_URL,但我不能设置它。这就是我想要的限制。我可以设置xhrSettings.type,只要它是POST、GET或PUT。

MF.factory.set.xhrSettings.type("DGD"); // No effect
MF.factory.set.xhrSettings.type("POST"); // data.xhrSettings.type = "POST"

然而,如果我做这个

a = MF.factory.get('xhrSettings');
a.type = "DGD";
console.log(MF.factory.get('xhrSettings'));

我看到data中的对象发生了变化,这使得我的整个概念变得毫无用处。我该如何解决这个问题?

这正是Object.freeze()的作用。

来自MDN:

Object.freeze((方法冻结对象:也就是说,阻止新的属性不会添加到其中;阻止现有属性被移除;并阻止现有属性或其可枚举性、可配置性或可写性。在里面本质上,对象实际上是不可变的。该方法返回对象被冻结。

注意:IE v8及以下版本不支持Object.freeze((。

我的建议是,与其返回对象,不如返回可以为可读性设置名称空间的属性:

  type = MF.factory.get( 'xhrSettings.type');
  type = "DGD";
  console.log(MF.factory.get('xhrSettings.type'));

这意味着您的data对象具有平面属性:

var data = {
    SITE_URL: document.URL.replace(/(https?:'/'/.+?)'/.*/, '$1'),
    "xhrSettings.type": 'GET',
    "xhrSettings.error": Function.prototype,
    "xhrSettings.max_upload_file_size": 8388608
}

等等。

set变成了一个实际的function,它检查要设置的属性,并执行任何您想要的操作(返回并忽略、抛出错误等(

Object.freeze(objectName);

其中"objectName"是要冻结的对象的名称。

我通过将this.get修改为以下来解决此问题

this.get = function(key){
    return (data[key] instanceof Object) ? Object.create(data[key]) : data[key];
}

一个解决方案可以是返回对象的副本。

使用下划线/lodash可以使用_.clone(),因此:

this.get = function(key){
    return _.clone(data[key]);
}

请记住,尽管该克隆只进行了一次浅层复制。因此,如果您需要它,您可以为此滥用jquery extend,并添加对数组的检查:

this.get = function(key){
    var x = data[key];
    return typeof x !== 'object' ? x :
        $.extend(true, (Array.isArray(x) ? [] : {}), x);
}

第二种解决方案仍然不完美,因为它无法处理循环对象,并且克隆getters/ssetter等可能存在问题。但对于简单的情况,即使是第一种解决方案也可以。