Defining read-only properties in JavaScript

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'

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.


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)

Tags:

Javascript