纯JavaScript是否可以实现只读属性?

58

查看mozilla文档,在正则表达式示例中(标题为“使用匹配结果创建数组”),我们有以下语句:

input: 只读属性,反映了正则表达式匹配的原始字符串。

index: 只读属性,是匹配项在字符串中零索引位置。

等等……在JavaScript中是否可以创建自己的对象,并具有只读属性,或者这是由特定浏览器实现的内置类型所保留的特权?


我在这里问了一个类似的问题:https://dev59.com/B2sz5IYBdhLWcg3wpZhF - Šime Vidas
如果想创建一个完全不可变的现有对象的副本(即具有深度只读/不可变属性的对象),可以参考这个链接:https://dev59.com/yHHYa4cB1Zd3GeqPHA9I#16064137 - Himanshu P
Object.seal 可以防止向对象添加新属性(但现有属性可以更改),而 Object.freeze 则可以防止对对象进行任何更改。Object.sealObject.freeze 都得到了很好的支持。 - Dave F
10个回答

74
使用任何实现ECMAScript 5的JavaScript解释器,您可以使用Object.defineProperty来定义只读属性。在宽松模式下,解释器将忽略对属性的写入操作;在严格模式下,它将抛出异常。
来自ejohn.org的示例:
var obj = {};
Object.defineProperty( obj, "<yourPropertyNameHere>", {
  value: "<yourPropertyValueHere>",
  writable: false,
  enumerable: true,
  configurable: true
});

2
注意:此内容与IE9+兼容,因此今天得到了相当好的支持。 - BenMorel

70

编辑:自从写下这个答案以来,使用Object.defineProperty的一种新而更好的方法已经在EcmaScript 5中标准化,并得到了较新浏览器的支持。请参见Aidamina的答案。如果您需要支持“旧”浏览器,可以使用本答案中的某种方法作为后备。


在Firefox、Opera 9.5+、Safari 3+、Chrome和IE(测试版本为v11)中,您可以定义getter和setter属性。如果您只定义了getter,则它实际上创建了一个只读属性。您可以在对象文字中定义它们,也可以通过调用对象上的方法来定义它们。
var myObject = {
    get readOnlyProperty() { return 42; }
};

alert(myObject.readOnlyProperty); // 42
myObject.readOnlyProperty = 5;    // Assignment is allowed, but doesn't do anything
alert(myObject.readOnlyProperty); // 42

如果您已经有一个对象,您可以调用__defineGetter____defineSetter__:
var myObject = {};
myObject.__defineGetter__("readOnlyProperty", function() { return 42; });

当然,这在网络上并不实用,因为它在Internet Explorer中无法工作。
您可以从John Resig的博客Mozilla开发者中心了解更多信息。

这不是当前规范的一部分。我相信它计划在下一个ECMAScript版本中实现,但现在它只是Mozilla扩展,在其他几个浏览器中也得到了支持。 - Matthew Crumley
现在两年后,它已经在下一个规格中了 =) - Claudiu
2
仍然不受IE支持 ;) - Sergiy Belozorov
@SergiyByelozyorov 我已经使用IE 11测试了上述代码,它运行良好 ;) - ComFreek
1
为了您的实用性,自IE 9版本以来就支持这种技术。这是一个很好的兼容性表:http://kangax.github.io/es5-compat-table/ - yodabar
显示剩余3条评论

6
在JavaScript中,可以通过getter方法实现只读属性,这通常被称为“模块”模式。
YUI博客对此有很好的解释:http://yuiblog.com/blog/2007/06/12/module-pattern/ 以下是文章的一部分:
YAHOO.myProject.myModule = function () {

//"private" variables:
var myPrivateVar = "I can be accessed only from within YAHOO.myProject.myModule.";

//"private" method:
var myPrivateMethod = function () {
    YAHOO.log("I can be accessed only from within YAHOO.myProject.myModule");
}

return  {
    myPublicProperty: "I'm accessible as YAHOO.myProject.myModule.myPublicProperty."
    myPublicMethod: function () {
        YAHOO.log("I'm accessible as YAHOO.myProject.myModule.myPublicMethod.");

        //Within myProject, I can access "private" vars and methods:
        YAHOO.log(myPrivateVar);
        YAHOO.log(myPrivateMethod());

        //The native scope of myPublicMethod is myProject; we can
        //access public members using "this":
        YAHOO.log(this.myPublicProperty);
    }
};

}(); // the parens here cause the anonymous function to execute and return

5

以下是只读属性或变量。

正如aidamina所说,这里有一个简短的测试代码,顺便说一下,现在非常有用,因为JQuery假装弃用了选择器属性。

<script>
Object.defineProperties(window, {
  "selector": { value: 'window', writable: false }
});

alert (window.selector);  // outputs window

selector ='ddd';          // testing because it belong to the global object
alert (window.selector);  // outputs window
alert (selector);         // outputs window

window.selector='abc';
alert (window.selector);   // outputs window
alert (selector);          // outputs window
</script>

所以,这里有一个只读属性或变量被测试。

我不敢相信这个没有更多的赞。你知道它是如何工作的吗? - czioutas

3

是的,我们可以在JavaScript中为对象设置只读属性。这可以通过私有变量和object.defineProperty()方法实现。

以下是一个示例,说明具有只读属性的对象:

function Employee(name,age){
    var _name = name;
    var _age = age;

    Object.defineProperty(this,'name',{
        get:function(){
            return _name;
        }
    })
}

var emp = new Employee('safeer',25);
console.log(emp.name); //return 'safeer'
emp.name='abc';
console.log(emp.name); //again return 'safeer', since name is read-only property

5年前给出的时候是一个不错的选择,但是JavaScript现在已经添加了私有字段,即_name变成了#name,它是真正的私有的。请参见下面的答案。 - jordanfb

2
这是Douglas Crockford关于“Javascript中的私有成员”的页面链接。如果只提供getter方法而没有setter,那么这些成员似乎只能读取而不能修改。
链接:http://javascript.crockford.com/private.html

私有变量和只读变量是不同的。私有变量(以及Crockford在JavaScript中实现它的方式)仅仅是“私有的”,这意味着它们只能在类(构造函数)内部访问,而不能从外部访问,因此你无法更改你无法访问的内容。只读属性可以从外部访问,但不能更改。 - yodabar
4
真的有必要对五年半前的仅链接回答进行负评吗?当时并没有现在这样的严格规定。 - Dexygen

1
你会看到我为颜色定义了一个setter和getter,以便进行修改。另一方面,一旦对象被定义,品牌就变成只读。我相信这就是你要寻找的功能。
        function Car(brand, color) {
            brand = brand || 'Porche'; // Private variable - Not accessible directly and cannot be frozen
            color = color || 'Red'; // Private variable - Not accessible directly and cannot be frozen
            this.color = function() { return color; }; // Getter for color
            this.setColor = function(x) { color = x; }; // Setter for color
            this.brand = function() { return brand; }; // Getter for brand
            Object.freeze(this); // Makes your object's public methods and properties read-only
        }

        function w(str) {
            /*************************/
            /*choose a logging method*/
            /*************************/
            console.log(str);
            // document.write(str + "<br>");
        }

        var myCar = new Car;
        var myCar2 = new Car('BMW','White');
        var myCar3 = new Car('Mercedes', 'Black');

        w(myCar.brand()); // returns Porche
        w(myCar.color()); // returns Red

        w(myCar2.brand()); // returns BMW
        w(myCar2.color()); // returns White

        w(myCar3.brand()); // returns Mercedes
        w(myCar3.color()); // returns Black

        // This works even when the Object is frozen
        myCar.setColor('Green');
        w(myCar.color()); // returns Green

        // This will have no effect
        myCar.color = 'Purple';
        w(myCar.color()); // returns Green
        w(myCar.color); // returns the method

        // This following will not work as the object is frozen
        myCar.color = function (x) {
            alert(x);
        };

        myCar.setColor('Black');
        w(
            myCar.color(
                'This will not work. Object is frozen! The method has not been updated'
            )
        ); // returns Black since the method is unchanged

上述内容已在Chromium版本41.0.2272.76 Ubuntu 14.04上进行了测试,并产生了以下输出:
          Porche
          Red
          BMW
          White
          Mercedes
          Black
          Green
          Green
          function () { return color; }
          Black

+1。这个答案暗示使用Object.freeze()。ECMAScript 5添加了Object.sealObject.freeze方法。seal方法将防止属性添加,但允许编写/编辑属性。Object.freeze方法将完全锁定一个对象,它们将保持冻结时的状态。 - S.Serpooshan
+1 对于红色的保时捷 - 有人知道什么是真正的跑车。无论如何,私有类字段几年前就被添加到JavaScript中了,请参见下面的答案。 - jordanfb

1
如果你想在运行时拥有一个只读属性,而不必启用“严格模式”,一种方法是定义一个“抛出异常的setter”。示例:
Object.defineProperty(Fake.prototype, 'props', {
  set: function() {
    // We use a throwing setter instead of frozen or non-writable props
    // because that won't throw in a non-strict mode function.
    throw Error();
  },
});

引用自 React


在你提到的链接中,它是一个模块文件,并且模块默认启用了严格模式吗?为什么他们需要抛出异常? - some_groceries

0

其他答案是正确的,但没有人提到私有类字段,这是定义只读属性的另一个选项(假设类在您的用例中有意义):

class Car {
  color = ''; // public field
  #serialNumber = ''; // # is a private field

  constructor(color, serialNumber) {
    this.color = color;
    this.#serialNumber = serialNumber;
  }

  // Public read-only access to serial number
  get serialNumber() {
    return this.#serialNumber;
  }
}

const car = new Car('black', '123XYZ');
car.color = 'red'; // Changes color
car.serialNumber; // Returns private value
car.serialNumber = 'other'; // Does nothing
car.serialNumber; // Returns private value as before

0

bob.js 框架 提供了一种声明只读属性的方法。在内部,它声明了一个私有字段,并公开了用于获取/设置该字段的函数。bob.js 提供了多种实现该目标的方式,具体取决于方便与特定目标。下面是一种使用面向对象实例的 Property 的方法(其他方法允许在对象本身上定义设置器/获取器):

var Person = function(name, age) {  
    this.name = new bob.prop.Property(name, true); 
    var setName = this.name.get_setter(); 
    this.age = new bob.prop.Property(age, true); 
    var setAge = this.age.get_setter();  
    this.parent = new bob.prop.Property(null, false, true);  
};  
var p = new Person('Bob', 20);  
p.parent.set_value(new Person('Martin', 50));  
console.log('name: ' + p.name.get_value());  
console.log('age: ' + p.age.get_value());  
console.log('parent: ' + (p.parent.get_value ? p.parent.get_value().name.get_value() : 'N/A')); 
// Output: 
// name: Bob 
// age: 20 
// parent: N/A 

最后,p.name.set_value 未定义,因为那是一个只读属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接