是否可以在 JavaScript Object Literal Notation 中创建只读成员?

Posted

技术标签:

【中文标题】是否可以在 JavaScript Object Literal Notation 中创建只读成员?【英文标题】:Is it possible to create read only members in JavaScript Object Literal Notation? 【发布时间】:2013-11-13 17:08:25 【问题描述】:

我有以下 javascript Object Literal Notation 对象

var Parameters= 
    modal_window:
        backdrop:true,
        keyboard:true,
        show:true,
        remote:false,
        type:
            normal:function()
                this.footer.button.accept.type='btn btn-primary';
                this.header.type='modal-header';
            ,
            success:function()
                this.footer.button.accept.type='btn btn-success';
                this.header.type='modal-header alert alert-success';
            ,
            info:function()
                this.footer.button.accept.type='btn btn-info';
                this.header.type='modal-header alert alert-info';
            ,
            error:function()
                this.footer.button.accept.type='btn btn-danger';
                this.header.type='modal-header alert alert-error';
            ,
            warning:function()
                this.footer.button.accept.type='btn btn-warning';
                this.header.type='modal-header alert';
            
        
    ,
    header:
        title:undefined,
        type:this.window.type.normal.header
    ,
    footer:
        button:
        
            accept:
                title:'Accept',
                click:undefined,
                type:undefined
            ,
            cancel:
                title:'Cancel',
                click:undefined
            
        
    
;

是否可以让header.type和footer.button.accept.type只读变量只能通过window.type.normal、window.type.success等来改变?

说明: 我想在这里做一些澄清。我的Parameters.header.type 应该是只读的并且应该有默认值。而当用户 选择例如Parameters.modal_window.type.normal 必须更改Parameters.header.type。

【问题讨论】:

不,在 javascript 中只读是不可能的。 只读可以使用Object.defineProperty,或者我使用带有闭包的函数。 好吧,如果您的代码有意义,我会这样做,但是当 this.window.type.normal.header 被调用时,this 是全局上下文(窗口)。 还有一件事,这是一个原型设计模式? @andy 我认为这实际上在现代 JavaScript 运行时是可能的(支持 getter/setter)。 【参考方案1】:

你可以让它们发挥作用,像这样:

header:
        title:undefined,
        type: function()
           return Parameters.modal_window.type.normal.header;
        
    

【讨论】:

嗯,它不会使它们成为只读的,但我认为这符合 op 试图实现的精神。 @pax162 我怎样才能改变它而不是从其他功能?【参考方案2】:

如果您需要支持 IE 8 或更早版本,您可以创建一个获取值的访问器方法,然后使用私有变量来存储实际数据。如果您适当地定义您的方法,则可以从它们设置私有变量,但不能由外部世界设置。在 IE8 中,无法定义只读属性,因此您必须改用访问器。

请参阅 Crockford 的关于私有成员数据的论文:http://javascript.crockford.com/private.html,详细了解如何设置访问者可以作为接口的私有数据。

如果您愿意使用 IE9 或更高版本,那么您可以通过 Object.defineProperty() 结合闭包中的私有变量使用 getter。如果没有 setter,则无法从外部设置,但在闭包中定义的方法(在 Crockford 的文章中描述)仍然可以设置私有变量的值。您将拥有一个只读属性,也可以由您自己的一些方法设置。

【讨论】:

这早于 getter 和 setter 定义的出现,不是吗? @Pointy - 你如何私下存储一些东西,然后只从某些方法访问它,而没有 crockford 的文章中描述的闭包概念? 你可以用闭包来做,所以那部分当然是准确的。使用 getter 和 setter,您可以通过让 getter 获取闭包值来使其看起来像那里有一个属性。 setter 要么什么都不做,要么做一些验证,或者其他什么。 @Pointy - 听起来不错。闭包加 getter 会给你一个只读属性,你可以从闭包中定义的其他方法修改它。据我了解,在Object.defineProperty() 中使用这些功能会迫使您使用 IE9 或更高版本,因此也必须接受。 为什么投反对票?这篇文章和 cmets 有什么不准确的地方?【参考方案3】:

您可以创建一个property 并将其设置为不可写。您的构造函数必须用属性替换值。如果属性返回的变量在闭包中被捕获并且没有暴露给其他任何东西,那么它将与只读一样好。如果不改变,你甚至不需要闭包,只需使用value 配置选项即可。

编辑:根据您的要求,

var Properties = function(obj) 
    var makePropRecursive = function(prop) 
        var old_prop = obj[prop];
        delete obj[prop];
        var prop_obj = ;
        for (var attr in old_prop) 
            if (old_prop.hasOwnProperty(attr)) 
                Object.defineProperty(prop_obj, attr, 
                    value: old_prop[attr],
                    writable: false,
                    enumerable: true
                );
            
        
        Object.defineProperty(obj, prop, 
            value: prop_obj,
            writable: false,
            enumerable: true
        );
    ;
    makePropRecursive('header');
    makePropRecursive('footer');
    return obj;
;

var props = new Properties(
    modal_window:
        backdrop:true,
        keyboard:true,
        show:true,
        remote:false,
        type:
            normal:function()
                this.footer.button.accept.type='btn btn-primary';
                this.header.type='modal-header';
            ,
            success:function()
                this.footer.button.accept.type='btn btn-success';
                this.header.type='modal-header alert alert-success';
            ,
            info:function()
                this.footer.button.accept.type='btn btn-info';
                this.header.type='modal-header alert alert-info';
            ,
            error:function()
                this.footer.button.accept.type='btn btn-danger';
                this.header.type='modal-header alert alert-error';
            ,
            warning:function()
                this.footer.button.accept.type='btn btn-warning';
                this.header.type='modal-header alert';
            
        
    ,
    header:
        title:"Whatever",
        type:"Type"
    ,
    footer:
        button:
        
            accept:
                title:'Accept',
                click:undefined,
                type:undefined
            ,
            cancel:
                title:'Cancel',
                click:undefined
            
        
    
);

console.log(props.header);
props.header = 17;
props.header.type = 18;
props.header.title = 19;
console.log(props.header);

props.header 不变:输出显示

Object title: "Whatever", type: "Type"
Object title: "Whatever", type: "Type" 

现在是凌晨 3 点,递归函数不是,所以你只能“修复”一个对象的一层;此外,如果将这些值复制到 this 而不是返回 obj 会更好;但打磨起来应该不会太难。

如果您需要更改值,您可以在构造函数中设置整个对象的私有副本,然后创建一个 getter (get: function(name) return stuff.from.the.original.object )。

【讨论】:

我想你像我第一次那样误解了这个问题。 @plalx:是的,使用静态只读属性。最后一段简要说明了如何为私有可写只读属性执行此操作,但我真的需要睡觉:p 我一读到它,你就得到了我的支持,如果它意味着什么。【参考方案4】:

在较新版本的 JavaScript 中,您可以定义属性访问的工作方式:

var yourObj = function() 
  var readOnly = "cannot be changed";
  return 
    get readOnly()  return readOnly; ,
    set readOnly(v)  return; ,

    specialSetter: function(something) 
      if (something == "magic number") 
        readOnly = "oops maybe it can";
      
    
  ;
();

现在代码可以得到这样的值:

var theValue = yourObj.readOnly;

无需进行函数调用。但是,如果它尝试更改值:

yourObj.readOnly = "hello world";

那么什么都不会发生。

setter 或任何其他函数都可以在需要时更新访问“readOnly”属性时将返回的值。但是,任何直接设置属性的尝试都不会执行任何操作(除非 setter 函数决定它喜欢该值)。

edit 您可能希望将“specialSetter”设为只读,尽管没有任何东西能够“闯入”闭包。此外,您可能想使用 Object.defineProperty 使“readOnly”不可写,但我不知道这是否能正常工作。

【讨论】:

您如何看待强制真正的隐私与使用诸如_myPrivateMember 之类的命名约定? 这个示例代码非常接近真正的隐私。我认为命名约定在美学上并不令人满意;) 是的,我也有同感,但我觉得大多数图书馆都不会费心实现真正的私有成员,至少从我目前所见的情况来看是这样。我们不得不在编写节省内存的代码和真正的封装代码之间做出选择,这太可悲了。我实际上只是为私有模块成员强制执行隐私,但从不为 classes.【参考方案5】:

怎么样:Object.freeze()

您可以在此处了解更多信息:MDN Object.freeze

所以:

Object.freeze(Parameters.modal_window.header);
...

然后在您希望能够设置它们的函数中,解冻它们、更改它们并重新冻结它们。

您绝对不能在程序的其他任何地方错误地更改冻结的对象。

这适用于 IE9+ chrome、firefox 和 safari。

【讨论】:

那么 nothing 可以改变属性。这与拥有只能在 setter 函数(可能应用验证规则或其他什么)控制下设置的私有属性不太一样。 @Pointy 好吧,他没有说他想要一个私有财产,他说他想要一个只读财产。这完成了。 最后一句谈到属性是可变的,但只能通过一些显式的 API。 我想让这个参数从外部只读。我不需要私人会员。它应该可以从外部访问以读取而不是写入。 @antindexer 对。您想控制如何更改属性;例如,检查它是否是数字或类似的东西。【参考方案6】:

不管大家怎么说,你可以在支持Object.defineProperty的现代浏览器中创建只读属性。

var obj = ;

Object.defineProperty(obj, 'someProp', 
    configurable: false,
    writable: false,
    value: 'initial value'
);

obj.someProp = 'some other value';

console.log(obj.someProp); //initial value

编辑:

再次阅读您的问题后,我了解到您的意思是真正的私有成员或私有变量。这可以通过使用闭包和自定义 getter/setter 来实现。

注意:为了示例,我简化了您的对象结构。

var Parameters = (function () 
    var headerType = 'some value'; //private variable

    return 
        modal_window: 
            type: 
                normal: function () 
                    //custom logic
                    headerType = 'some new value'; //set private variable
                
            
        ,
        header: 
            get type()  return headerType;  //define a getter only

            //for older browsers, you could just define a normal function
            //which you would have to access like Parameters.header.type()
            //type: function ()  return headerType; 
        
    ;

)();

var header = Parameters.header;

console.log(header.type); //some value
header.type = 'some other val';
console.log(header.type); //some value
Parameters.modal_window.type.normal();
console.log(header.type); //some new value

既然我们知道可以强制执行真正的隐私,我不确定这是否真的值得。强制执行真正的隐私会使设计复杂化并降低可测试性(视情况而定)。一种非常流行的方法是使用诸如_myPrivateVar 之类的命名约定来简单地识别私有成员。这清楚地表明了意图并告诉程序员他们应该将该成员视为私人成员。

【讨论】:

这是完美的工作我投票给你的答案。我也只是尝试其他人的答案。所以请等待我的决定。 @antindexer 你忘了吗? ;) 这个问题是不可写(只读)属性不能被任何代码写入。 OP 不是在寻找那种解决方案,就像我阅读它的方式一样。 @Pointy,答案的第二部分允许修改变量。 @plalx 是的,好的,我明白了。这或多或少也是我的回答所做的。【参考方案7】:

您可以使用以下显示模块模式来隐藏变量并防止它们被更改,但这不会阻止任何人更改可访问的“类型”函数。

在下面的代码中,将 header 属性更改为 _header 并制成一个函数。属性类型已更改为 _type 并通过使用对象表示法包装返回以将“类型”作为函数而不是属性返回来隐藏。有人可以通过重写将类型函数更改为他们想要的任何内容,但他们不能更改 _type 的值。

var Parameters = function () 
var _modal_window = function modal_window() 
    var backdrop = true,
    keyboard = true,
    show = true,
    remote = false;
    return 
        type: 
            normal: function () 
                this.footer.button.accept.type = 'btn btn-primary';
                this.header.type = 'modal-header';
            ,
            success: function () 
                this.footer.button.accept.type = 'btn btn-success';
                this.header.type = 'modal-header alert alert-success';
            ,
            info: function () 
                this.footer.button.accept.type = 'btn btn-info';
                this.header.type = 'modal-header alert alert-info';
            ,
            error: function () 
                this.footer.button.accept.type = 'btn btn-danger';
                this.header.type = 'modal-header alert alert-error';
            ,
            warning: function () 
                this.footer.button.accept.type = 'btn btn-warning';
                this.header.type = 'modal-header alert';
            
        
    ;
();
var _header = function header() 
    var _type = 'This causes error';//this.window.type.normal.header;
    return 
        title: undefined, type: function ()  return _type; 
    ;
();
var _footer = function footer() 
    return 
        button:
    
        accept: 
            title: 'Accept',
            click: undefined,
            type: undefined
        ,
        cancel: 
            title: 'Cancel',
            click: undefined
        
    
    ;
();
return 
    modal_window: _modal_window,
    header: _header,
    footer: _footer
;
();

【讨论】:

以上是关于是否可以在 JavaScript Object Literal Notation 中创建只读成员?的主要内容,如果未能解决你的问题,请参考以下文章

javascript判断一个对象是否是空对象,localStorage和sessionStorage区别

javascript 检查Array中是否存在Object

JavaScript isEmpty() - 检查Object是否为空

JavaScript中in操作符(for..in)Object.keys()和Object.getOwnPropertyNames()的区别

JavaScript中in操作符(for..in)Object.keys()和Object.getOwnPropertyNames()的区别

[转] JavaScript中in操作符(for..in)Object.keys()和Object.getOwnPropertyNames()的区别