Protected properties using Name objects
Herby Vojčík
herby at mailbox.sk
Fri Jun 15 13:23:03 PDT 2012
Hi.
I have hard time to put my objections well. Different words appear in my
mind like "too monolithic", "too spaghetti", ... but none of them is
very good at explaining the problem.
It seems to me that this make thing complex and brittle. Protected
overides writable and configurable, so their meaning is compromised.
Having fourth one added to the three increases the number of
combinations. Having only one or none protected key brings problems with
later unprotecting / reprotecting when there are more than one subset of
consumers.
I include a counter-proposal, which defines aliases for existing
properties, albeit with different access.
Brandon Benvie wrote:
> Currently there's no way to have a data property that is writable by
> some but not all. This can't even really be achieved with accessors or
> proxies directly. Rather, the underlying data is mutated through some
> other avenue, and the value returned on access comes from this secondary
> source. Neither method of course is remotely as efficient as a real data
> property as either.
>
> Using Name objects, it seems like a full fledged elegant "protected"
> property access API is possible to describe. For this context, I define
> a protected property as a property on an object who's description or
> value can only be modified after passing some access check. There's two
> levels of access covered by what I propose: the base level ability to
> write a property's data (`writable` descriptor property) and the ability
> to configure a property (`configurability` descriptor property).
>
> For writing to an object, this concept allows for a property to be {
> writable: false } but be written through a Name object corresponding to
> the property.
>
> For modifying the property's descriptor, a fourth boolean descriptor
> attribute is now defined named 'protected'. When a property is
> protected, the Name object associated with it is required in order to
> modify it using Object.defineProperty. A property which is protected
> acts the same as a non-configurable property when modification is
> attempted using the property's string name. A property can become
> unprotected later, unlike a non-configurable property, and can be
> modified internally to engine as well. Protected only acts like
> non-configurable for unauthorized access from code.
>
>
> Code (also available at https://gist.github.com/2938186)
>
>
> let x = { a: 10 };
>
> // #############################
> // ### Protecting a property ###
> // #############################
>
> let writeA = Object.protectProperty(x, 'a');
let writeA = Object.aliasProperty(x, 'a', { enumerable: true,
configurable: true, writable: true});
> // -- or --
> let writeA = new Name;
> Object.protectProperty(x, 'a', writeA);
let writeA = new Name;
Object.aliasProperty(x, 'a', writeA, { enumerable: true, configurable:
true, writable: true}); // or writeA as last
>
>
> // #######################################
> // ### Describing a protected property ###
> // #######################################
>
> Object.isProtected(x, 'a');
> // true
N/A, not needed. Instead maybe this, if needed:
Object.isAlias(x, 'a')
// false
Object.isAlias(x, writeA);
// true
>
> Object.getOwnPropertyDescriptor(x, 'a')
> // { writable: false, default protectProperty to set non-writable?
> // protected: true,
> // enumerable: true,
> // configurable: true }
returns present state of property 'a' as usual. It can have false anywhere.
> // #####################################
> // ### Writing to protected property ###
> // #####################################
>
> x.a = 50
> // silent fail normally, throws in strict "Cannot assign to protected
> read-only property 'x'"
// behaves as normal proerty (fails silently or not if non-writable)
> x[writeA] = 50;
> // x is { a: 50 }
>
>
> // ##################################################
> // ### Modifying a protected property description ###
> // ##################################################
>
> Object.defineProperty(x, 'a', { writable: true });
> // throw "Cannot modify protected property 'a'"
// again, behaves normally, fails if nonconfigurable.
> Object.defineProperty(x, writeA, { writable: true });
> // { writable: true,
> // protected: true,
> // enumerable: true,
> // configurable: true }
// behaves normally like on 'a' except it has access of writeA.
// So if it 'a' was non-writable, configurable and enumerable, returns:
// { writable: true,
// enumerable: true.
// configurable: true }
> // ###############################
> // ### Unprotecting a property ###
> // ###############################
>
> Object.defineProperty(x, writeA, { protected: false });
> // -- or --
> Object.protectProperty(x, writeA);
Object.removeAlias(x, writeA);
// true
// removes writeA alias
// It is different than delete x.writeA, which
// deletes 'a' if writeA has enough access.
> // #########################################
> // ### Changing a protected property key ###
> // #########################################
>
> Object.defineProperty(x, writeA, { protected: false });
> let newWriteA = Object.protectProperty(x, 'a');
> // -- or --
> let newWriteA = new Name();
> Object.protectProperty(x, writeA, newWriteA);
>
> x[writeA] = 100;
> // throw 'Invalid protected property assignment'
not applicable
> // from
> http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes
>
> const pHealth = new Name();
> const wAlive = new Name();
>
> class Monster {
> constructor(name, health) {
> this.name <http://this.name> = name;
> this.alive = true;
> this[pHealth] = health;
> Object.protectProperty(this, 'alive', wAlive);
Object.aliasProperty(this, 'alive', wAlive,{writable:true,...});
Object.defineProperty(this, 'alive', {writable:false, ...});
// above line is needed - you restrict the access manually,
// it is not automatic
> }
>
> attack(target) {
> log('The monster attacks ' + target);
> }
>
> defend(roll) {
> let damage = Math.random() * roll;
> this[pHealth] -= damage;
> if (this[pHealth] <= 0) {
> this[wAlive] = false;
> }
> }
>
> set health(value) {
> if (value < 0) {
> throw new Error('Health must be non-negative.');
> }
> this[pHealth] = value;
> }
> }
As the last thing, I introduce the twist which could be possible with
this API, and provide shorter variant of constructor. The twist is, you
can define property and alias in reversed order:
constructor(name, health) {
this.name = name;
this[wAlive] = true;
this[pHealth] = health;
Object.aliasProperty(this,wAlive,'alive',{writable:false,...});
}
That is, you could define public restricted alias on private full-access
property.
Herby
More information about the es-discuss
mailing list