This page provides additional examples for Object.defineProperty()
.
Using binary flags instead of a property descriptor object
If you have to define many properties through the Object.defineProperty()
method, you can use the same descriptor object for each property, redefining it from time to time through binary flags.
var oDesc = {}; function setProp (nMask, oObj, sKey, vVal_fGet, fSet) { if (nMask & 8) { // accessor descriptor if (vVal_fGet) { oDesc.get = vVal_fGet; } else { delete oDesc.get; } if (fSet) { oDesc.set = fSet; } else { delete oDesc.set; } delete oDesc.value; delete oDesc.writable; } else { // data descriptor if (arguments.length > 3) { oDesc.value = vVal_fGet; } else { delete oDesc.value; } oDesc.writable = Boolean(nMask & 4); delete oDesc.get; delete oDesc.set; } oDesc.enumerable = Boolean(nMask & 1); oDesc.configurable = Boolean(nMask & 2); Object.defineProperty(oObj, sKey, oDesc); return oObj; } /* * :: function setProp :: * * nMask is a bitmask: * flag 0x1: property is enumerable, * flag 0x2: property is configurable, * flag 0x4: property is writable, * flag 0x8: property is accessor descriptor. * oObj is the object on which to define the property; * sKey is the name of the property to be defined or modified; * vVal_fGet is the value to assign to a data descriptor or the getter function * to assign to an accessor descriptor (depending on the bitmask); * fSet is the setter function to assign to an accessor descriptor; * * Bitmask possible values: * * 0 : readonly data descriptor - not configurable, not enumerable (0000). * 1 : readonly data descriptor - not configurable, enumerable (0001). * 2 : readonly data descriptor - configurable, not enumerable (0010). * 3 : readonly data descriptor - configurable, enumerable (0011). * 4 : writable data descriptor - not configurable, not enumerable (0100). * 5 : writable data descriptor - not configurable, enumerable (0101). * 6 : writable data descriptor - configurable, not enumerable (0110). * 7 : writable data descriptor - configurable, enumerable (0111). * 8 : accessor descriptor - not configurable, not enumerable (1000). * 9 : accessor descriptor - not configurable, enumerable (1001). * 10 : accessor descriptor - configurable, not enumerable (1010). * 11 : accessor descriptor - configurable, enumerable (1011). * * Note: If the flag 0x8 is setted to "accessor descriptor" the flag 0x4 (writable) * will be ignored. If not, the fSet argument will be ignored. */ // creating a new empty object var myObj = {}; // adding a writable data descriptor - not configurable, not enumerable setProp(4, myObj, 'myNumber', 25); // adding a readonly data descriptor - not configurable, enumerable setProp(1, myObj, 'myString', 'Hello world!'); // adding an accessor descriptor - not configurable, enumerable setProp(9, myObj, 'myArray', function() { for (var iBit = 0, iFlag = 1, aBoolArr = [false]; iFlag < this.myNumber + 1 || (this.myNumber & iFlag); iFlag = iFlag << 1 ) { aBoolArr[iBit++] = Boolean(this.myNumber & iFlag); } return aBoolArr; }, function(aNewMask) { for (var nNew = 0, iBit = 0; iBit < aNewMask.length; iBit++) { nNew |= Boolean(aNewMask[iBit]) << iBit; } this.myNumber = nNew; }); // adding a writable data descriptor (undefined value) - configurable, enumerable setProp(7, myObj, 'myUndefined'); // adding an accessor descriptor (only getter) - configurable, enumerable setProp(11, myObj, 'myDate', function() { return new Date(); }); // adding an accessor descriptor (only setter) - not configurable, not enumerable setProp(8, myObj, 'myAlert', null, function(sTxt) { alert(sTxt); }); myObj.myAlert = myObj.myDate.toLocaleString() + '\n\n' + myObj.myString + '\nThe number ' + myObj.myNumber + ' represents the following bitmask: ' + myObj.myArray.join(', ') + '.'; // listing the enumerable properties var sList = 'Here are the enumerable properties of myObj object:\n'; for (var sProp in myObj) { sList += '\nmyObj.' + sProp + ' => ' + myObj[sProp] + ';' } alert(sList);
Create a new non-native Object.setProperty()
method
You can do the same thing with a descriptor object obtained through an anonymous constructor and an Object
's custom method named setProperty()
:
// creating a new Object method named Object.setProperty() new (function() { var oDesc = this; Object.setProperty = function(nMask, oObj, sKey, vVal_fGet, fSet) { if (nMask & 8) { // accessor descriptor if (vVal_fGet) { oDesc.get = vVal_fGet; } else { delete oDesc.get; } if (fSet) { oDesc.set = fSet; } else { delete oDesc.set; } delete oDesc.value; delete oDesc.writable; } else { // data descriptor if (arguments.length > 3) { oDesc.value = vVal_fGet; } else { delete oDesc.value; } oDesc.writable = Boolean(nMask & 4); delete oDesc.get; delete oDesc.set; } oDesc.enumerable = Boolean(nMask & 1); oDesc.configurable = Boolean(nMask & 2); Object.defineProperty(oObj, sKey, oDesc); return oObj; }; })(); // creating a new empty object var myObj = {}; // adding a writable data descriptor - not configurable, not enumerable Object.setProperty(4, myObj, 'myNumber', 25); // adding a readonly data descriptor - not configurable, enumerable Object.setProperty(1, myObj, 'myString', 'Hello world!'); // etc. etc.
Object.setProperty()
method could be also a proposal for a possible new JavaScript native method (see ECMAScript bug 335).Syntax
Object.setProperty(bitmask, obj, prop[, value/getter[, setter]])
Parameters
bitmask
- The descriptor bitmask (see below).
obj
- The object on which to define the property.
prop
- The name of the property to be defined or modified.
value/getter
- Optional. The value to assign to a data descriptor or the getter function to assign to an accessor descriptor (depends on the bitmask).
setter
- Optional. The setter function to assign to an accessor descriptor. If the flag
0x8
is setted to data descriptor this argument will be ignored.
Description
The non-native Object.setProperty()
method works like the native Object.defineProperty()
method, except for the descriptor object which is replaced with a descriptor bitmask. The bitmask
argument has the following structure:
- flag
0x1
- The property is enumerable.
- flag
0x2
- The property is configurable.
- flag
0x4
- The property is writable.
- flag
0x8
- The property is an accessor descriptor.
So, the descriptor bitmask can have these possible numeric values:
0
: The bitmask represents a readonly data descriptor — not configurable, not enumerable (0000
).1
: The bitmask represents a readonly data descriptor — not configurable, enumerable (0001
).2
: The bitmask represents a readonly data descriptor — configurable, not enumerable (0010
).3
: The bitmask represents a readonly data descriptor — configurable, enumerable (0011
).4
: The bitmask represents a writable data descriptor — not configurable, not enumerable (0100
).5
: The bitmask represents a writable data descriptor — not configurable, enumerable (0101
).6
: The bitmask represents a writable data descriptor — configurable, not enumerable (0110
).7
: The bitmask represents a writable data descriptor — configurable, enumerable (0111
).8
: The bitmask represents an accessor descriptor — not configurable, not enumerable (1000
).9
: The bitmask represents an accessor descriptor — not configurable, enumerable (1001
).10
: The bitmask represents an accessor descriptor — configurable, not enumerable (1010
).11
: The bitmask represents an accessor descriptor — configurable, enumerable (1011
).
0x8
is set to accessor descriptor the flag 0x4
(writable) will be ignored. If not, the setter
argument will be ignored.HTMLSelectElement.selectedIndex implementation
You can use the Object.defineProperty()
method with native objects also. The following example shows how to implement the HTMLSelectElement
's selectedIndex
property in radio button groups.
<!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Radio group selectedIndex example</title> <script type="text/javascript"> Object.defineProperty(NodeList.prototype, 'selectedIndex', { get: function() { var nIndex = this.length - 1; while (nIndex > -1 && !this[nIndex].checked) { nIndex--; } return nIndex; }, set: function(nNewIndex) { if (isNaN(nNewIndex)) { return; } var nOldIndex = this.selectedIndex; if (nOldIndex > -1) { this[nOldIndex].checked = false; } if (nNewIndex > -1) { this[nNewIndex].checked = true; } }, enumerable: true, configurable: false }); // try it! function checkForm() { var nSelectedIndex = document.myForm.myRadioGroup.selectedIndex; if (nSelectedIndex < 0) { alert('Select a gadget!!'); return false; } alert('Congratulations!! You selected the ' + document.myForm.myRadioGroup[nSelectedIndex].value + '.'); return true; } </script> </head> <body> <form name="myForm" onsubmit="return(checkForm());"> <fieldset><legend>Select a gadget</legend> <p><input type="radio" name="myRadioGroup" id="ourShirt" value="shirt" /> <label for="ourShirt">shirt</label><br /> <input type="radio" name="myRadioGroup" id="ourPants" value="pants" /> <label for="ourPants">pants</label><br /> <input type="radio" name="myRadioGroup" id="ourBelt" value="belt" /> <label for="ourBelt">belt</label><br /> <input type="radio" name="myRadioGroup" id="ourShoes" value="shoes" /> <label for="ourShoes">shoes</label></p> <p><span style="cursor:pointer;text-decoration:underline;color:#0000ff;" onclick="document.myForm.myRadioGroup.selectedIndex=2;">Select our favorite gadget ;-)</span></p> <p><input type="submit" value="Order!" /> </fieldset> </form> </body> </html>