Skip to content

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

  1. If Type(O) is not Object throw a TypeError exception.
  2. Let props be ToObject(Properties).
  3. Let names be an internal list containing the names of each enumerable own property of props.
  4. Let descriptors be an empty internal List.
  5. For each element P of names in list order, a. Let descObj be the result of calling the [[Get]] internal method of props with P as the argument. b. Let desc be the result of calling ToPropertyDescriptor with descObj 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. Append desc to the end of descriptors.
  6. For each element desc of descriptors in list order, a. Call the [[DefineOwnProperty]] internal method of O with arguments P, desc, and true.
  7. Return O.

Notes:

  • In Step 6.a P should refer to the name related to the descriptor being processed, but there is no assignment for P 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]]:

  1. If Type(O) is not Object throw a TypeError exception.
  2. Let props be ToObject(Properties).
  3. Let descriptors be an empty internal Object. (Note: we assume that the object has enumeration order matching property insertion order.)
  4. For each enumerable property P of props (in normal enumeration order), a. Let descObj be the result of calling the [[Get]] internal method of props with P as the argument. b. Let desc be the result of calling NormalizePropertyDescriptor with descObj 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 of descriptors with P, desc and true as arguments.
  5. For each enumerable property P of descriptors (in insertion order), a. Let desc be the result of calling the [[Get]] internal method of descriptors with P as the argument. (Note: this is guaranteed to succeed and yield a valid descriptor object.) b. Call the Object.defineProperty() built-in method with the arguments O, P and desc, ignoring its result value. (Note: this call may fail due to an exception.)
  6. 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.