Exposed Object.defineProperty()
Original algorithm
The algorithm is specified in E5 Section 15.2.3.6:
- If
Type(O)is notObjectthrow aTypeErrorexception. - Let
namebeToString(P). (Note: this may have side effects.) - Let
descbe the result of callingToPropertyDescriptorwithAttributesas the argument. - Call the
[[DefineOwnProperty]]internal method ofOwith argumentsname,desc, andtrue. (Note: the last argument,true, is theThrowflag.) - 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 withThrowset 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 notObjectthrow aTypeErrorexception. - Let
namebeToString(P). (Note: this may have side effects.) - If
Type(O)is notObjectthrow aTypeErrorexception. - Let
descbe 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]]!==undefinedandIsCallable(desc.[[Get]])===false, then throw aTypeErrorexception. - If
O.[[HasProperty]]("set")===true, then: a. Setdesc.[[Set]]toO.[[Get]]("set"). b. Ifdesc.[[Set]]!==undefinedandIsCallable(desc.[[Set]])===false, then throw aTypeErrorexception. - If either
desc.[[Get]]ordesc.[[Set]]are present, then: a. If eitherdesc.[[Value]]ordesc.[[Writable]]are present, then throw aTypeErrorexception. - Let
Throwbetrue. - Set
pendingWriteProtecttofalse. - If
Ois not anArrayobject, goto SKIPARRAY. - Let
oldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) - If
Pis"length", then a. If the[[Value]]field ofDescis absent, then goto SKIPARRAY. b. LetnewLenbeToUint32(Desc.[[Value]]). c. IfnewLenis 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 tofalsein case any elements cannot be deleted. 2. SetpendingWriteProtecttotrue. 3. SetDesc.[[Writable]]totrue. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
Pis an array index (E5 Section 15.4), then: a. LetindexbeToUint32(P). b. Goto REJECT ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefined: a. Ifextensibleisfalse, then goto REJECT. b. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Descalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). (This also covers the case where every field inDescis absent.) - If the
[[Configurable]]field ofcurrentisfalsethen a. Goto REJECT, if the[[Configurable]]field ofDescis true. b. Goto REJECT, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Goto REJECT, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. Goto REJECT, If the[[Writable]]field ofcurrentisfalse, and the[[Value]]field ofDescis present, andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Goto REJECT, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Goto REJECT, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto the value of the field. - SUCCESS: If
Ois anArrayobject: a. IfPis"length", andnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. IfpendingWriteProtectistrue, update the property ("length") to have[[Writable]] = false. 4. Goto REJECT, ifshortenSucceededisfalse. b. IfPis an array index andindex>=oldLen: 1. Update the"length"property ofOto the valueindex + 1. This always succeeds, because we've checked in the pre-step that the"length"is writable, and sincePis an array index property, the length must still be writable here. - If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. IfIsAccessorDescriptor(Desc)istrue, then: a. Call the[[Delete]]internal method ofmappassingP, andfalseas the arguments. (This removes the magic binding forP.) 2. Else (Descmay be generic or data descriptor): a. IfDesc.[[Value]]is present, then: 1. Call the[[Put]]internal method ofmappassingP,Desc.[[Value]], andThrowas the arguments. (This updates the bound variable value.) b. IfDesc.[[Writable]]is present and its value isfalse, then: 1. Call the[[Delete]]internal method ofmappassingPandfalseas arguments. (This removes the magic binding forP, and must happen after a possible update of the variable value.) - Return
O. - REJECT: If
Throwistrue, then throw aTypeErrorexception, otherwise returnfalse. - REJECTRANGE: Throw a
RangeErrorexception. Note that this is unconditional (thrown even ifThrowisfalse).
Notes:
- Step 3 is redundant (it comes from
ToPropertyDescriptor()because of step 1. - Since
Throwis alwaystrue, step 12 can be removed and step 32 changed to throwTypeErrorunconditionally. Note thatThrowis 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 theThrowflag is not visible to the setter).
Some cleanup
- If
Type(O)is notObjectthrow aTypeErrorexception. - Let
namebeToString(P). (Note: this may have side effects.) - Let
descbe 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]]!==undefinedandIsCallable(desc.[[Get]])===false, then throw aTypeErrorexception. - If
O.[[HasProperty]]("set")===true, then: a. Setdesc.[[Set]]toO.[[Get]]("set"). b. Ifdesc.[[Set]]!==undefinedandIsCallable(desc.[[Set]])===false, then throw aTypeErrorexception. - If either
desc.[[Get]]ordesc.[[Set]]are present, then: a. If eitherdesc.[[Value]]ordesc.[[Writable]]are present, then throw aTypeErrorexception. - Set
pendingWriteProtecttofalse. - If
Ois not anArrayobject, goto SKIPARRAY. - Let
oldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) - If
Pis"length", then a. If the[[Value]]field ofDescis absent, then goto SKIPARRAY. b. LetnewLenbeToUint32(Desc.[[Value]]). c. IfnewLenis 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 tofalsein case any elements cannot be deleted. 2. SetpendingWriteProtecttotrue. 3. SetDesc.[[Writable]]totrue. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
Pis an array index (E5 Section 15.4), then: a. LetindexbeToUint32(P). b. Goto REJECT ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefined: a. Ifextensibleisfalse, then goto REJECT. b. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Descalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). (This also covers the case where every field inDescis absent.) - If the
[[Configurable]]field ofcurrentisfalsethen a. Goto REJECT, if the[[Configurable]]field ofDescis true. b. Goto REJECT, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Goto REJECT, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. Goto REJECT, If the[[Writable]]field ofcurrentisfalse, and the[[Value]]field ofDescis present, andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Goto REJECT, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Goto REJECT, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto the value of the field. - SUCCESS: If
Ois anArrayobject: a. IfPis"length", andnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. IfpendingWriteProtectistrue, update the property ("length") to have[[Writable]] = false. 4. Goto REJECT, ifshortenSucceededisfalse. b. IfPis an array index andindex>=oldLen: 1. Update the"length"property ofOto the valueindex + 1. This always succeeds, because we've checked in the pre-step that the"length"is writable, and sincePis an array index property, the length must still be writable here. - If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. IfIsAccessorDescriptor(Desc)istrue, then: a. Call the[[Delete]]internal method ofmappassingP, andfalseas the arguments. (This removes the magic binding forP.) 2. Else (Descmay be generic or data descriptor): a. IfDesc.[[Value]]is present, then: 1. Call the[[Put]]internal method ofmappassingP,Desc.[[Value]], andtrueas the arguments. (This updates the bound variable value. Note that theThrowflag is irrelevant,trueused now.) b. IfDesc.[[Writable]]is present and its value isfalse, then: 1. Call the[[Delete]]internal method ofmappassingPandfalseas arguments. (This removes the magic binding forP, and must happen after a possible update of the variable value.) - Return
O. - REJECT: Throw a
TypeErrorexception. - REJECTRANGE: Throw a
RangeErrorexception.