defineProperty/getProperty design sketch

Mark S. Miller erights at google.com
Wed Apr 23 08:21:50 PDT 2008


The following are comments I sent Allen on a slightly earlier draft. I think
all these are still applicable.

---------- Forwarded message ----------
From: Mark S. Miller <erights at google.com>
Date: Tue, Apr 22, 2008 at 4:32 PM
Subject: Re: define/getProperty meta operations
To: Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com>


 *Problem:  Design Pattern for Supporting Meta Operations Upon Objects  --
> Design Sketch & Notes*
>
> *Not Yet Spec. or Proposal*
>
> *Allen Wirfs-Brock*
>
> * *
>
> ECMAScript needs to support an enhanced API for manipulating and query the
> structural and behavior definition of dynamic objects. Example of such
> operations include the definition of setter/getter properties and the
> setting of property attributes such as [[dontenum]].  A single design
> pattern needs to be established to guide the design of these new functions.
>
> *General Approach*
>
> Add new static functions as ReadOnly, DontDelete properties of Object
> constructor. Parameterize with literal arguments enable possible analysis
> and static implementation of complex objects. Minimize number of entry
> points using complex patterns of optional arguments. Use JSON-style
> object-literal to implement object pattern.  Use same object pattern for
> both property definition/query.
>

Hi Allen,

As discussed on the phone, I like a lot of this! However, I would like to
use a nested object literal to describe a set of properties, and I want to
avoid using a function's declared name as a way to cutely specify the
corresponding property name. Below each of your use cases, I'll show the
alternative I have in mind.



> *Define/Modify a property and/or property attributes*
>
> Object.defineProperty(obj, descriptor)
>
Object.defineProperties(<obj>, <multiDescriptor>)

<multiDescriptor> = { (<propertyName>: <descriptor>)* }


> Add to *obj* the property described by *descriptor.* Set properties of the
> attribute as described by *descriptor*.  Return *false* if is not
> successful.
>
throws if not successful.


>  *Use case: modify attributes of an existing property.*
>
> Descriptor = { name: *string**, *[, writable: *boolean*] [, enumerable: *
> boolean*] [, deletable: *boolean*]}
>
Without the "name: string" part, since that comes from the multiDescriptor.


>  If *obj* has a local property whose name is the value of the *name*element of the descriptor set the attributes of the property according to
> the values of the writable, enumerable, and deletable elements of the
> descriptor.  Fail if the property does not exist  (or add fixed/frozen
> attribute)
>
If the property does not yet exist, then all unenumerated attributes of an
explicitly defined property should default to false. If the property already
exists, then unenumerated attributes should default to their existing value.


> Example:
>
> Object.defineProperty(Array.prototype, {name: "some", enumerable: false});
>
Object.defineProperties(Array.prototype, {some: {enumerable: false}});


>  *Use case: add a data property to an object.*
>
> Descriptor = { name: *string**, *value: *object *[, writable: *true*] [,
> enumerable: *false*] [, deletable: *false*]}
>
> Add to obj a property whose name is the value of the *name* element of the
> descriptor and whose initial value  is the value of the value element of the
> descriptor. Set the attributes to the specified or default values according
> to the elements of the descriptor.  Fail if the property already exists and
> is not writeable (or add fixed attribute)
>
> Example:
>
> var obj = new Object();
>
> Object.addProperty(obj, {name: "x", value: 0});
>
> Object addProperty(obj, {name: "y", value: 0});
>
> Object.addProperty(obj, {name: "pi", value: 3.14159, writable: false});
>

Object.defineProperties(obj, {x: {value: 0}, y: {value: 0}, {pi: {writable:
false}});


>  *Use case: add a method property to an object.*
>
> Descriptor = { name: *string**, *method: *function *[, writable: *false*]
> [, enumerable: *false*] [, deletable: *false*]}
>
>
>
> Add to obj a property whose name is the value of the *name* element of the
> descriptor and whose value  is the value of the method element of the
> descriptor. Set the attributes of the specified or default values according
> to the elements of the descriptor.  Fail if the property already exists and
> is not writeable (or add fixed attribute) or if the value of the *method*element is not a function.
>
> Notes:  The primary difference from the data property use case is that the
> writable attribute defaults to false rather than true.
>
Unenumerated attributes of newly defined properties should always default to
false. Are there other reasons to distinguish methods from values? I will
proceed for now assuming not.



> Examples:
>
> Object.addProperty(obj, {name: doSomeWork, method: function ()
> {this.work()});
>
> Aternative form assuming existence of Function.prototype.name:
>
> Object.addProperty(obj, {method: function doSomeWork () {this.work()});
>
Object.defineProperties(obj, {doSomeWork: {value: function()
{this.work();}}});


>  *Use case: add a getter/setter property to an object.*
>
> Descriptor = { name: *string**, *getter: *function *[, writable: *false*]
> [, enumerable: *false*] [, deletable: *false*]}
>
> Or
>
> Descriptor = {name: *string**, *getter: *function*, setter:  *function*
>
> [, writable: *true*] [, enumerable: *false*] [, deletable: *false*]}
>
>
>
But without the "name: string" part again.

>
> Add to obj a property getter (and setter) whose name is the value of the *
> name* element of the descriptor and whose value  is the value of the *
> getter* (and *setter*) element(s) of the descriptor. Set the attributes of
> the specified or default values according to the elements of the
> descriptor.  Fail if the property already exists and is not writeable (or
> add fixed attribute) or if the value of the *getter *or* setter* element
> is not a function.
>
> Examples:
>
> var privateprop_y = new Object();
>
> var obj = new Object();
>
> Object.addProperty(obj, {name: privateprop_y, value: 0});
>
> Object addProperty(obj, {name: "y",
>
> getter: function () {return this[privateprop]},
>
> setter: function (arg) {this[privateprop] = arg}  });
>

Whoa! In ES3, all property names are strings. Above, you seem to suggest
allowing first class anon object refs as keys. This is eventually attractive
on many grounds, but is *way* too big a step for ES3.1. Below, I will assume
instead the property name "privateprop_y", which, of course, isn't private
at all.


> Alternative form assuming existence of Function.prototype.name:
>
> Object addProperty(obj, { getter: function y() {return this[privateprop]},
>
>    setter: function y(arg) {this[privateprop] = arg}  });
>

Object.defineProperties(obj, {
    privateprop_y: {value: 0, writable: true},
    y: {getter: function() {return this.privateprop_y;},
         setter: function(arg) {this.privateprop_y = arg;}}
});


> *Query existence and attributes of an object's properties*
>
Object.getProperties(obj) => <multiDescriptor>


> * *
>
> Object.getProperty(obj, name)
>
> If *obj* has a locally defined property whose name is *name* return a
> fully populated proper descriptor object as used with
> Object.defineProperty.  Return *undefined* if the named property does not
> exist.
>
As if defined by

Object.getProperty = function(obj, name) { return
Object.getProperties(obj)[name]; };


>  *Other possible object constructor functions:*
>
> Object.freeze(obj)
>
as well as Object.seal(obj) which seals without freezing. A sealed object is
non-dynamic -- new properties cannot be added to it, and its existing
properties become {deletable: false}. A frozen object is a sealed object
with the additional constraint that all its existing properties become
{writable: false}.

> Object.defineProperties(obj, {[ {*descriptor*}, propName2: {*descriptor*
> },…])
>
> Object.getProperties(obj)
>
> Crock's beget function, etc.
>
> Create object with properties
>
>
>
> *Sketch:  Defining a "fixture"*
>
> var fixture = new Object();
>
> Object.defineProperty(fixture, {method: function method1 (…) {…}});
>
> Object.defineProperty(fixture, {method: function method2 (…) {…}});
>
> Object.defineProperty(fixture, {getter: function prop3 () {…}, setter:
> function (arg) {…}});
>
>>
> Object.freeze(proto)
>
var fixture = new Object();
Object.defineProperties(fixture, {
    method1: {value: function(...){...}},
    method2: {value: function(...){...}},
    prop3: {getter: function(){...}, setter: function(arg){...}}
});
Object.seal(fixture); // for the last line above, is this what you meant?


> var template = new Object();
>
> Object.defineProperty(template, {name: "var1", value: undefined});
>
> Object.defineProperty(template, {name: "var2", value: undefined});
>
>>
> Object.freeze(template);
>
>>
> return Object.beget(proto,template);  //new object with properties form
> template and prototype proto
>
I still don't like the idea of extending beget with copying behavior.






-- 
   Cheers,
   --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080423/bddc3b0b/attachment-0002.html 


More information about the Es4-discuss mailing list