mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
7.4 KiB
200 lines
7.4 KiB
=================================
|
|
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.
|
|
|