Make a property that is read-only to the outside world, but my methods can still set

后端 未结 3 430
眼角桃花
眼角桃花 2020-12-28 08:49

In JavaScript (ES5+), I\'m trying to achieve the following scenario:

  1. An object (of which there will be many separate instances) each with a read-only property
3条回答
  •  悲哀的现实
    2020-12-28 09:24

    OK, so for a solution you need two parts:

    • a size property which is not assignable, i.e. with writable:true or no setter attributes
    • a way to change the value that size reflects, which is not .size = … and that is public so that the prototype methods can invoke it.

    @plalx has already presented the obvious way with a second "semiprivate" _size property that is reflected by a getter for size. This is probably the easiest and most straightforward solution:

    // declare
    Object.defineProperty(MyObj.prototype, "size", {
        get: function() { return this._size; }
    });
    // assign
    instance._size = …;
    

    Another way would be to make the size property non-writable, but configurable, so that you have to use "the long way" with Object.defineProperty (though imho even too short for a helper function) to set a value in it:

    function MyObj() { // Constructor
        // declare
        Object.defineProperty(this, "size", {
            writable: false, enumerable: true, configurable: true
        });
    }
    // assign
    Object.defineProperty(instance, "size", {value:…});
    

    These two methods are definitely enough to prevent "shoot in the foot" size = … assignments. For a more sophisticated approach, we might build a public, instance-specific (closure) setter method that can only be invoked from prototype module-scope methods.

    (function() { // module IEFE
        // with privileged access to this helper function:
        var settable = false;
        function setSize(o, v) {
            settable = true;
            o.size = v;
            settable = false;
        }
    
        function MyObj() { // Constructor
            // declare
            var size;
            Object.defineProperty(this, "size", {
                enumerable: true,
                get: function() { return size; },
                set: function(v) {
                    if (!settable) throw new Error("You're not allowed.");
                    size = v;
                }
            });
            …
        }
    
        // assign
        setSize(instance, …);
    
        …
    }());
    

    This is indeed fail-safe as long as no closured access to settable is leaked. There is also a similar, popular, little shorter approach is to use an object's identity as an access token, along the lines of:

    // module IEFE with privileged access to this token:
    var token = {};
    
    // in the declaration (similar to the setter above)
    this._setSize = function(key, v) {
        if (key !== token) throw new Error("You're not allowed.");
            size = v;
    };
    
    // assign
    instance._setSize(token, …);
    

    However, this pattern is not secure as it is possible to steal the token by applying code with the assignment to a custom object with a malicious _setSize method.

提交回复
热议问题