80

Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?

Object.defineProperty( obj, 'prop', {
    get: function () {
        return val;
    }
});

The result should be (for val = 'test'):

obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only

This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • Possible duplicate: http://stackoverflow.com/questions/366047/can-read-only-properties-be-implemented-in-pure-javascript (If you don't want to support older browsers, your method is the best) – James Allardice Oct 13 '11 at 16:34

6 Answers6

132

You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:

var obj = {};
Object.defineProperty(obj, "prop", {
    value: "test",
    writable: false
});

As mentioned in the comments, the writable option defaults to false so you can omit it in this case:

Object.defineProperty(obj, "prop", {
    value: "test"
});

This is ECMAScript 5 so won't work in older browsers.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 1
    I'm not sure if my code and your code produce the exact same result "on the outside", but your method is without doubt the more proper way to do it. – Šime Vidas Oct 13 '11 at 17:05
  • As per MDN documentation, `Object.defineProperty(obj, "prop", {value:"test"});` is equivalent to the code above because writable is set to false by default. – aquawicket Oct 30 '18 at 17:14
  • 3
    Note that **subProperties** of an eventual object/array **can still be modified**. Only the "prop" key in this case cannot be reassigned. – TOPKAT Sep 01 '19 at 20:08
8

In new browsers or node.js it is possible to use Proxy to create read-only object.

var obj = {
    prop: 'test'
}

obj = new Proxy(obj ,{
    setProperty: function(target, key, value){
        if(target.hasOwnProperty(key))
            return target[key];
        return target[key] = value;
    },
    get: function(target, key){
        return target[key];
    },
    set: function(target, key, value){
        return this.setProperty(target, key, value);
    },
    defineProperty: function (target, key, desc) {
        return this.setProperty(target, key, desc.value);
    },
    deleteProperty: function(target, key) {
        return false;
    }
});

You can still assign new properties to that object, and they would be read-only as well.

Example

obj.prop
// > 'test'

obj.prop = 'changed';
obj.prop
// > 'test'

// New value
obj.myValue = 'foo';
obj.myValue = 'bar';

obj.myValue
// > 'foo'
Firanolfind
  • 1,559
  • 2
  • 17
  • 36
1

In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.

class SetOnlyOnce {
    #innerObj = {}; // private field, not accessible from outside

    getCurrentPropertyName(){
        const stack = new Error().stack; // probably not really performant method
        const name = stack.match(/\[as (\w+)\]/)[1];
        return name;
    }

    getValue(){
        const key = this.getCurrentPropertyName();

        if(this.#innerObj[key] === undefined){
            throw new Error('No global param value set for property: ' + key);
        }

        return this.#innerObj[key];
    }

    setValue(value){
        const key = this.getCurrentPropertyName();

        if(this.#innerObj[key] !== undefined){
            throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
        }

        this.#innerObj[key] = value;
    }
}


class GlobalParams extends SetOnlyOnce {
    get couchbaseBucket() { return this.getValue()}
    set couchbaseBucket(value){ this.setValue(value)}

    get elasticIndex() { return this.getValue()}
    set elasticIndex(value){ this.setValue(value)}   
}

const _globalParams = new GlobalParams();

_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';

console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)

_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Adam
  • 1,796
  • 18
  • 19
0

Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:

var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);

//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));

//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');

//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.

I hope it helps.

Tengiz
  • 8,011
  • 30
  • 39
  • Wait, `.namedProp(obj, 'foo')`, creates `.get_foo()`,/`.set_foo()` on the object itself? That's not very efficient. I think I'd go with a wrapper, e.g. `X(obj).set('foo')`/`X(obj).get('foo')`. – Šime Vidas Nov 03 '12 at 13:32
  • For clarification: I think we are talking about two different things. You possibly want to keep the real object unchanged while having a wrapper around it; but I suggested to change the real object and have functions representing properties. Because for backwards compatibility, old-style JavaScript properties may be treated as fields. Thus, you would need to have accessors (functions) for it (plus I would suggest to remove traditional JS properties from the real object, thus have complete control over the properties). As about wrappers, that's also not bad, but differs from my approach. – Tengiz Nov 03 '12 at 23:56
0

10+ years after this question, ES6 is not a concern even in old browsers. If the object functionality can be satisfied by class construct with static members, then it can be implemented as

const obj = class {
  static #prop = "test";
  static get prop() { return obj.#prop }
};

console.log(obj.prop); // test
obj.prop = 'changed';
console.log(obj.prop); // test

Note that class constructs are special functions, not objects:

typeof obj; // function
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
-3

I tried and it Works ...

element.readOnly = "readOnly"  (then .readonly-> true)
element.readOnly = ""  (then .readonly-> false)
Piotr Labunski
  • 1,638
  • 4
  • 19
  • 26