In JavaScript (ES5+), I'm trying to achieve the following scenario:
- An object (of which there will be many separate instances) each with a read-only property
.size
that can be read from the outside via direct property read, but cannot be set from the outside. - The
.size
property must be maintained/updated from some methods which are on the prototype (and should stay on the prototype). - My API is already defined by a specification so I can't modify that (I'm working on a polyfill for an already-defined ES6 object).
- I'm mostly trying to prevent people from shooting themselves in the foot accidentally and don't really have to have bulletproof read-only-ness (though the more bullet-proof it is, the better), so I am willing to compromise some on side door access to the property as long as directly setting
obj.size = 3;
isn't allowed.
I'm aware that I could use a private variable declared in the constructor and set up a getter to read it, but I would have to move the methods that need to maintain that variable off the prototype and declare them inside the constructor also (so they have access to the closure containing the variable). For this particular circumstance, I'd rather not take my methods off the prototype so I'm searching for what the other options might be.
What other ideas might there be (even if there are some compromises to it)?
OK, so for a solution you need two parts:
size
property which is not assignable, i.e. withwritable:true
or nosetter
attributessize
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 forsize
. This is probably the easiest and most straightforward solution:Another way would be to make the
size
property non-writable, but configurable, so that you have to use "the long way" withObject.defineProperty
(though imho even too short for a helper function) to set a value in it: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.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: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.I've been doing this lately:
I think that covers all of the OP's points. I haven't done any performance profiling. For my use cases, correctness is more important.
Thoughts?
Honestly, I find that there's too many sacrifices to be made in order to enforce true privacy in JS (unless you are defining a module) so I prefer to rely on naming conventions only such as
this._myPrivateVariable
.This is a clear indicator to any developer that they shouldn't be accessing or modifying this member directly and it doesn't require to sacrifice the benefits of using prototypes.
If you need your
size
member to be accessed as a property you will have no other choice but to define a getter on the prototype.Another approach I've seen is to use the module pattern in order to create a
privates
object map which will hold individual instances private variables. Upon instantiation, a read-only private key gets assigned on the instance and that key is then used to set or retrieve values from theprivates
object.As you may have noticed, this pattern is interesting but the above implementation is flawed because private variables will never get garbage collected even if there's no reference left to the instance object holding the key.
However, with ES6 WeakMaps this problem goes away and it even simplifies the design because we can use the object instance as the key instead of a number like we did above. If the instance gets garbage collected the weakmap will not prevent the garbage collection of the value referenced by that object.