Defining read-only properties in JavaScript
Asked Answered
O

6

81

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...

Overtrump answered 13/10, 2011 at 16:32 Comment(1)
Possible duplicate: #366547 (If you don't want to support older browsers, your method is the best)Influent
M
136

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.

Moonseed answered 13/10, 2011 at 16:46 Comment(3)
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.Jacobinism
As per MDN documentation, Object.defineProperty(obj, "prop", {value:"test"}); is equivalent to the code above because writable is set to false by default.Bleak
Note that subProperties of an eventual object/array can still be modified. Only the "prop" key in this case cannot be reassigned.Bluebell
S
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'
Sartorius answered 9/6, 2018 at 6:13 Comment(0)
P
2

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)
Palomino answered 30/9, 2020 at 23:7 Comment(0)
I
1

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
Inutile answered 1/7, 2023 at 8:34 Comment(0)
F
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.

Fogbound answered 3/11, 2012 at 13:23 Comment(2)
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').Jacobinism
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.Fogbound
T
-3

I tried and it Works ...

element.readOnly = "readOnly"  (then .readonly-> true)
element.readOnly = ""  (then .readonly-> false)
Trinitroglycerin answered 8/6, 2020 at 11:55 Comment(2)
Provide your answer in code snippet. It's not clear enough nowOdellodella
@Stavros Sfikas, question is not about HTML elements.Peckham

© 2022 - 2024 — McMap. All rights reserved.