Exposed Object.defineProperties()
Implementation approach discussion
Since Object.defineProperty()
and Object.defineProperties()
are such expensive functions (from a code footprint point of view), we'd really like to have only one implementation with some wrappers. For instance, we could have an actual implementation of Object.defineProperty()
and then have Object.defineProperties()
call it as a helper (or vice versa).
Considering the case where Object.defineProperties()
would use Object.defineProperty()
as a helper, the Object.defineProperties()
algorithm is unfortunate: it coerces all property descriptors with ToPropertyDescriptor()
and puts them on an internal list (descriptors
) before doing any operations on the target object. The coercion includes property descriptor validation, and implies some way of storing the internal descriptors (other than local variables).
Note that the ToPropertyDescriptor()
coercion may also have arbitrary user visible side effects because it calls [[Get]]
on the relevant properties. The [[Get]]
may invoke a getter call, which may in pathological cases even modify the other descriptors -- creating both an ordering and a call count dependency. Consider the pathological case:
var desc2 = { value: 0 };
var desc1 = {
get value() {
print("desc1 value getter");
desc2.value++; // increment for every call
return "test";
}
};
var descs = { foo: desc1, bar: desc2 };
var o = {};
Object.defineProperties(o, descs);
print(o.foo); // should print test
print(o.bar); // should print 1, as getter is called exactly once
If the implementation were to, for instance, call ToPropertyDescriptor()
twice (once to validate, discarding any results, and a second time when calling Object.defineProperty()
internally as a helper), it would fail the above test.
On the other hand, if the implementation simply called Object.defineProperty()
for each descriptor in turn, it would not be compliant if there is an invalid descriptor in the Properties
argument list of Object.defineProperties()
. No changes to the target object can be made if there is an invalid descriptor in the list.
There are other pathological cases too, e.g. a getter removing elements from the Properties
argument of Object.defineProperties()
.
Another implementation approach is to make Object.defineProperties()
the main algorithm and have Object.defineProperty()
be a wrapper around it. This works but still has issues:
Object.defineProperties()
still needs to have a list of coerced descriptors internally, which implies some storage (other than local variables) for coerced (internal) descriptors.Object.defineProperty()
would need to create a temporary object for containing the one property descriptor it gets as an input, e.g.:Object.defineProperty(o, 'foo', { value: 'bar' });
needs to become:
Object.defineProperties(o, { foo: { value: 'bar' }});
A way around creating an internal representation for partially populated descriptors is to use an internal ECMAScript object representing a validated and normalized descriptor with all property values already coerced and checked; any getter calls would be done during coercion and the final value would be a plain one. In the pathological example above, the internal descriptors could be:
{
foo: {
value: "test"
},
bar: {
value: 1
}
}
The coercions could then be executed first, and the coerced descriptors then given one at a time (as ECMAScript objects) to Object.defineProperty()
.
This would eliminate any side effects of the coercion and would allow validation of the descriptors before any object changes. The downside is the need for an additional helper, and creating temporary objects for each Object.defineProperties()
(but not Object.defineProperty()
) call.
This is the current implementation approach. The coercion helper is defined as NormalizePropertyDescriptor
in the restatements section and will be inlined below. Note that this helper is not part of the E5 specification.
Original algorithm
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
props
beToObject(Properties)
. - Let
names
be an internal list containing the names of each enumerable own property ofprops
. - Let
descriptors
be an empty internal List. - For each element
P
ofnames
in list order, a. LetdescObj
be the result of calling the[[Get]]
internal method ofprops
withP
as the argument. b. Letdesc
be the result of callingToPropertyDescriptor
withdescObj
as the argument. (Note: this step may fail due for invalid property descriptors, and may have user visible side effects due to potential getter calls.) c. Appenddesc
to the end ofdescriptors
. - For each element
desc
ofdescriptors
in list order, a. Call the[[DefineOwnProperty]]
internal method ofO
with argumentsP
,desc
, andtrue
. - Return
O
.
Notes:
- In Step 6.a
P
should refer to the name related to the descriptor being processed, but there is no assignment forP
after step 5. This seems like a small typo in the specification.
Using NormalizePropertyDescriptor
Below, the standard algorithm has been changed to use NormalizePropertyDescriptor()
and to call Object.defineProperty()
instead of [[DefineOwnProperty]]
:
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
props
beToObject(Properties)
. - Let
descriptors
be an empty internal Object. (Note: we assume that the object has enumeration order matching property insertion order.) - For each enumerable property
P
ofprops
(in normal enumeration order), a. LetdescObj
be the result of calling the[[Get]]
internal method ofprops
withP
as the argument. b. Letdesc
be the result of callingNormalizePropertyDescriptor
withdescObj
as the argument. (Note: this step may fail due for invalid property descriptors, and may have user visible side effects due to potential getter calls.) c. Call the[[Put]]
internal method ofdescriptors
withP
,desc
andtrue
as arguments. - For each enumerable property
P
ofdescriptors
(in insertion order), a. Letdesc
be the result of calling the[[Get]]
internal method ofdescriptors
withP
as the argument. (Note: this is guaranteed to succeed and yield a valid descriptor object.) b. Call theObject.defineProperty()
built-in method with the argumentsO
,P
anddesc
, ignoring its result value. (Note: this call may fail due to an exception.) - Return
O
.
Changing [[DefineOwnProperty]]
to Object.defineProperty()
should be semantically correct. Consider the steps of Object.defineProperty()
in E5 Section 15.2.3.6:
- Step 1: already covered by step 1 above.
- Step 2: a no-op because all property names (
P
) above are naturally strings. - Step 3: guaranteed to succeed and be side-effect free, and to produce the same result as it normally would.
- Step 4: makes a call to
[[DefineOwnProperty]]
- Step 5: return value is ignored.