Exposed Object.defineProperty()
Original algorithm
The algorithm is specified in E5 Section 15.2.3.6:
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
name
beToString(P)
. (Note: this may have side effects.) - Let
desc
be the result of callingToPropertyDescriptor
withAttributes
as the argument. - Call the
[[DefineOwnProperty]]
internal method ofO
with argumentsname
,desc
, andtrue
. (Note: the last argument,true
, is theThrow
flag.) - Return
O
.
The algorithm returns the object, which allows chaining; for instance:
var o = {};
Object.defineProperty(o, 'foo',
{ value: 'bar' }
).seal();
ToPropertyDescriptor()
is a helper called only from Object.defineProperty()
and Object.defineProperties()
. It converts a property descriptor expressed as an ECMAScript object into a "specification descriptor", doing boolean coercions and cross checking the descriptor. For instance, ToPropertyDescriptor()
will reject any property descriptor which contains fields indicating it is both a data property descriptor and an accessor property descriptor. Example from Node / V8:
> var o = {};
> Object.defineProperty(o, 'foo',
... { value: 'bar', set: function() {} });
TypeError: Invalid property. A property cannot both
have accessors and be writable or have a value, #<Object>
The result of the property descriptor conversion is an "internal descriptor". Note that unlike when dealing with existing object properties, this descriptor may not be fully populated, i.e. may be missing fields. From an implementation perspective this means that the descriptor needs to be represented differently. The current implementation doesn't have an explicit representation for the "internal descriptor" which exists for the duration of Object.defineProperty()
; the descriptor is represented by a bunch of local variables indicating the presence and coerced values of the descriptor fields (for instance: has_writable
and is_writable
are separate variables).
The ToPropertyDescriptor()
algorithm is reformulated in the restatements section.
Other notes:
- The key is coerced to a string leniently while the object is just checked and never coerced.
[[DefineOwnProperty]]
is always called withThrow
set totrue
, so the implementation doesn't need to expose a "throw flag".
First draft
Starting from the original algorithm and inlining both ToPropertyDescriptor()
and the [[DefineOwnProperty]]
algorithm with exotic behaviors, we get:
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
name
beToString(P)
. (Note: this may have side effects.) - If
Type(O)
is notObject
throw aTypeError
exception. - Let
desc
be a new, empty Property Descriptor. - If
O.[[HasProperty]]("enumerable")
===true
, then setdesc.[[Enumerable]]
toToBoolean(O.[[Get]]("enumerable"))
. - If
O.[[HasProperty]]("configurable")
===true
, then setdesc.[[Configurable]]
toToBoolean(O.[[Get]]("configurable"))
. - If
O.[[HasProperty]]("value")
===true
, then setdesc.[[Value]]
toO.[[Get]]("value")
. - If
O.[[HasProperty]]("writable")
===true
, then setdesc.[[Writable]]
toToBoolean(O.[[Get]]("writable"))
. - If
O.[[HasProperty]]("get")
===true
, then: a. Setdesc.[[Get]]
toO.[[Get]]("get")
. b. Ifdesc.[[Get]]
!==undefined
andIsCallable(desc.[[Get]])
===false
, then throw aTypeError
exception. - If
O.[[HasProperty]]("set")
===true
, then: a. Setdesc.[[Set]]
toO.[[Get]]("set")
. b. Ifdesc.[[Set]]
!==undefined
andIsCallable(desc.[[Set]])
===false
, then throw aTypeError
exception. - If either
desc.[[Get]]
ordesc.[[Set]]
are present, then: a. If eitherdesc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception. - Let
Throw
betrue
. - Set
pendingWriteProtect
tofalse
. - If
O
is not anArray
object, goto SKIPARRAY. - Let
oldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
P
is"length"
, then a. If the[[Value]]
field ofDesc
is absent, then goto SKIPARRAY. b. LetnewLen
beToUint32(Desc.[[Value]])
. c. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, goto REJECTRANGE. d. SetDesc.[[Value]]
tonewLen
. e. IfnewLen
>=oldLen
, then goto SKIPARRAY. f. Goto REJECT ifoldLenDesc.[[Writable]]
isfalse
. g. IfDesc.[[Writable]]
has the valuefalse
: 1. Need to defer setting the[[Writable]]
attribute tofalse
in case any elements cannot be deleted. 2. SetpendingWriteProtect
totrue
. 3. SetDesc.[[Writable]]
totrue
. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
P
is an array index (E5 Section 15.4), then: a. Letindex
beToUint32(P)
. b. Goto REJECT ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
: a. Ifextensible
isfalse
, then goto REJECT. b. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. c. Else,Desc
must be an accessor Property Descriptor so, 1. Create an own accessor property namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Desc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). (This also covers the case where every field inDesc
is absent.) - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Goto REJECT, if the[[Configurable]]
field ofDesc
is true. b. Goto REJECT, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Goto REJECT, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
from a data property to an accessor property. Preserve the existing values of the converted property's[[Configurable]]
and[[Enumerable]]
attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property namedP
of objectO
from an accessor property to a data property. Preserve the existing values of the converted property's[[Configurable]]
and[[Enumerable]]
attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. Goto REJECT, If the[[Writable]]
field ofcurrent
isfalse
, and the[[Value]]
field ofDesc
is present, andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Goto REJECT, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - SUCCESS: If
O
is anArray
object: a. IfP
is"length"
, andnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. IfpendingWriteProtect
istrue
, update the property ("length"
) to have[[Writable]] = false
. 4. Goto REJECT, ifshortenSucceeded
isfalse
. b. IfP
is an array index andindex
>=oldLen
: 1. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds, because we've checked in the pre-step that the"length"
is writable, and sinceP
is an array index property, the length must still be writable here. - If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. IfIsAccessorDescriptor(Desc)
istrue
, then: a. Call the[[Delete]]
internal method ofmap
passingP
, andfalse
as the arguments. (This removes the magic binding forP
.) 2. Else (Desc
may be generic or data descriptor): a. IfDesc.[[Value]]
is present, then: 1. Call the[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as the arguments. (This updates the bound variable value.) b. IfDesc.[[Writable]]
is present and its value isfalse
, then: 1. Call the[[Delete]]
internal method ofmap
passingP
andfalse
as arguments. (This removes the magic binding forP
, and must happen after a possible update of the variable value.) - Return
O
. - REJECT: If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
. - REJECTRANGE: Throw a
RangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
).
Notes:
- Step 3 is redundant (it comes from
ToPropertyDescriptor()
because of step 1. - Since
Throw
is alwaystrue
, step 12 can be removed and step 32 changed to throwTypeError
unconditionally. Note thatThrow
is also given as a parameter in step 30.b.2.1 as an argument for an internal[[Put]]
to the parameter map. This actually has no effect on behavior (the internal setter will be called, and theThrow
flag is not visible to the setter).
Some cleanup
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
name
beToString(P)
. (Note: this may have side effects.) - Let
desc
be a new, empty Property Descriptor. - If
O.[[HasProperty]]("enumerable")
===true
, then setdesc.[[Enumerable]]
toToBoolean(O.[[Get]]("enumerable"))
. - If
O.[[HasProperty]]("configurable")
===true
, then setdesc.[[Configurable]]
toToBoolean(O.[[Get]]("configurable"))
. - If
O.[[HasProperty]]("value")
===true
, then setdesc.[[Value]]
toO.[[Get]]("value")
. - If
O.[[HasProperty]]("writable")
===true
, then setdesc.[[Writable]]
toToBoolean(O.[[Get]]("writable"))
. - If
O.[[HasProperty]]("get")
===true
, then: a. Setdesc.[[Get]]
toO.[[Get]]("get")
. b. Ifdesc.[[Get]]
!==undefined
andIsCallable(desc.[[Get]])
===false
, then throw aTypeError
exception. - If
O.[[HasProperty]]("set")
===true
, then: a. Setdesc.[[Set]]
toO.[[Get]]("set")
. b. Ifdesc.[[Set]]
!==undefined
andIsCallable(desc.[[Set]])
===false
, then throw aTypeError
exception. - If either
desc.[[Get]]
ordesc.[[Set]]
are present, then: a. If eitherdesc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception. - Set
pendingWriteProtect
tofalse
. - If
O
is not anArray
object, goto SKIPARRAY. - Let
oldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
P
is"length"
, then a. If the[[Value]]
field ofDesc
is absent, then goto SKIPARRAY. b. LetnewLen
beToUint32(Desc.[[Value]])
. c. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, goto REJECTRANGE. d. SetDesc.[[Value]]
tonewLen
. e. IfnewLen
>=oldLen
, then goto SKIPARRAY. f. Goto REJECT ifoldLenDesc.[[Writable]]
isfalse
. g. IfDesc.[[Writable]]
has the valuefalse
: 1. Need to defer setting the[[Writable]]
attribute tofalse
in case any elements cannot be deleted. 2. SetpendingWriteProtect
totrue
. 3. SetDesc.[[Writable]]
totrue
. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
P
is an array index (E5 Section 15.4), then: a. Letindex
beToUint32(P)
. b. Goto REJECT ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
: a. Ifextensible
isfalse
, then goto REJECT. b. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. c. Else,Desc
must be an accessor Property Descriptor so, 1. Create an own accessor property namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Desc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). (This also covers the case where every field inDesc
is absent.) - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Goto REJECT, if the[[Configurable]]
field ofDesc
is true. b. Goto REJECT, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Goto REJECT, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
from a data property to an accessor property. Preserve the existing values of the converted property's[[Configurable]]
and[[Enumerable]]
attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property namedP
of objectO
from an accessor property to a data property. Preserve the existing values of the converted property's[[Configurable]]
and[[Enumerable]]
attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. Goto REJECT, If the[[Writable]]
field ofcurrent
isfalse
, and the[[Value]]
field ofDesc
is present, andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Goto REJECT, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - SUCCESS: If
O
is anArray
object: a. IfP
is"length"
, andnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. IfpendingWriteProtect
istrue
, update the property ("length"
) to have[[Writable]] = false
. 4. Goto REJECT, ifshortenSucceeded
isfalse
. b. IfP
is an array index andindex
>=oldLen
: 1. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds, because we've checked in the pre-step that the"length"
is writable, and sinceP
is an array index property, the length must still be writable here. - If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. IfIsAccessorDescriptor(Desc)
istrue
, then: a. Call the[[Delete]]
internal method ofmap
passingP
, andfalse
as the arguments. (This removes the magic binding forP
.) 2. Else (Desc
may be generic or data descriptor): a. IfDesc.[[Value]]
is present, then: 1. Call the[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andtrue
as the arguments. (This updates the bound variable value. Note that theThrow
flag is irrelevant,true
used now.) b. IfDesc.[[Writable]]
is present and its value isfalse
, then: 1. Call the[[Delete]]
internal method ofmap
passingP
andfalse
as arguments. (This removes the magic binding forP
, and must happen after a possible update of the variable value.) - Return
O
. - REJECT: Throw a
TypeError
exception. - REJECTRANGE: Throw a
RangeError
exception.