In Javascript, we can add a property to an object with:
obj.property = value;
However, sometimes we would like to have fine-grained control over properties.
With Object.defineProperty()
, we can decide:
Descriptor | Purpose | Default |
---|---|---|
value | Property value | undefined |
writable | Whether the property can be assigned | false |
get | Getter of the property | undefined |
set | Setter of the property | undefined |
enumerable | Whether the property is visible to for-each loop | false |
configurable | Whether the property can be redefined | false |
Object.defineProperty(obj, name, desc)
expects three arguments:
- The
obj
argument is the object to which we would like to add a new property. - The
name
argument is the name of the new property. - The
desc
argument is the descriptor object of the new property. Ifenumerable
,configurable
, orwritable
are not defined in the descriptor object, then the default valuefalse
will be assumed.
Value and Writable
The simplest usage is to define a property with a value
. By default,
it will become a read-only property. Every assignment to the property will be
silently ignored.
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', { value: 32 });
// Get the value.
console.log(t.fahrenheit); // Prints 32.
// Set the value.
t.fahrenheit = 5; // Ignored silently.
console.log(t.fahrenheit); // Continue to print 32.
Under the strict mode, assigning to a non-writable property will raise a
TypeError
instead:
'use strict'; // CHANGED
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', { value: 32 });
try {
// Set the value.
t.fahrenheit = 5; // Raise a TypeError exception.
} catch (ex) {
if (ex instanceof TypeError) {
console.log('CAUGHT EXPECTED EXCEPTION.');
} else {
console.trace(ex);
}
}
To define a property that can be assigned, we have to set writable
to
true
:
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
value: 32,
writable: true // CHANGED
});
t.fahrenheit = 41;
console.log(t.fahrenheit); // Prints 41
From some perspective, the code snippet above is similar to the following simple assignment:
var t = Object.create(null);
t.fahrenheit = 32; // Similar
t.fahrenheit = 41;
console.log(t.fahrenheit); // Prints 41
Note
The difference between two code snippets is the enumerability of the defined property. Read Enumerable section for more information.
Get and Set
Imagine a senario: we would like to create an object to represent a temperature in both celsius and fahrenheit. Besides, we would like to maintain the correspondence between them. What can we do? Getters and setters are the answers.
We can define a get
or set
function with
Object.defineProperty()
. The get
function can read values from
other properties or compute the result on-the-fly. Similarly, the set
function can write values to other properties. For example:
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
'get': function () {
return (this.celsius * 9 / 5 + 32);
},
'set': function (x) {
this.celsius = (x - 32) * 5 / 9;
}
});
t.celsius = 0;
console.log(t.fahrenheit + ' F'); // Prints: "32 F"
t.celsius = 50;
console.log(t.fahrenheit + ' F'); // Prints "122 F"
t.fahrenheit = 77;
console.log(t.fahrenheit + ' F'); // Prints "77 F"
console.log(t.celsius + ' C'); // Prints "25 C"
Similar to writable: false
, if the set
descriptor is not
specified, then the assignment operations to the property will be ignored
silently:
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
'get': function () {
return (this.celsius * 9 / 5 + 32);
}
});
t.celsius = 0;
t.fahrenheit = 77; // Silently ignored.
console.log(t.fahrenheit + ' F'); // Prints "32 F"
console.log(t.celsius + ' C'); // Prints "0 C"
Under the strict mode, a TypeError
will be raised instead:
'use strict'; // CHANGED
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
'get': function () {
return (this.celsius * 9 / 5 + 32);
}
});
try {
t.fahrenheit = 77;
} catch (ex) {
if (ex instanceof TypeError) {
console.log('CAUGHT EXPECTED EXCEPTION.');
} else {
console.trace(ex);
}
}
Note
get
and set
can not be mixed with value
and
writable
.
Enumerable
By default, when Object.defineProperty()
defines a new property, the
newly defined property won't be enumerable, i.e. the newly defined property
won't be enumerated in the for-each loop:
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', { value: 32 });
// Enumerate the keys.
for (var key in t) {
// This loop won't print anything because the `fahrenheit` property is
// NOT enumerable.
console.log('Found: ' + key);
}
If we wish to enumerate the newly defined property, then we can set
enumerable
to true
:
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
enumerable: true, // CHANGED
value: 32
});
// Enumerate the keys.
for (var key in t) {
console.log('Found: ' + key); // Prints: "Found: fahrenheit"
}
Configurable
By default, the property defined by Object.defineProperty()
can not be
redefined or reconfigured. A TypeError
will be raised if you try to do
so.
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', { value: 32 });
try {
// Redefine the property.
Object.defineProperty(t, 'fahrenheit', { value: 50 });
} catch (ex) {
if (ex instanceof TypeError) {
console.log('CAUGHT EXPECTED EXCEPTION.');
} else {
console.trace(ex);
}
}
If you really wish to redefine the property afterwards, then you should
set configurable
to true
.
var t = Object.create(null);
Object.defineProperty(t, 'fahrenheit', {
configurable: true, // CHANGED
value: 32
});
console.log(t.fahrenheit); // Prints: 32
// Redefine the property.
Object.defineProperty(t, 'fahrenheit', { value: 50 });
console.log(t.fahrenheit); // Prints: 50
Conclusion
In this post, we have learned several usages of Object.defineProperty()
:
- One can define a property with
value
and control its immutability withwritable
. - One can define a property with getter and setter functions. The getter and
setter functions are specified by
get
andset
descriptors respectively. - To enumerate a property in a for-each loop, specify
enumerable
. - To allow the reconfiguration of a property, specify
configurable
.
I hope you enjoy this post. Feel free to let me know if you have any comments.
References
- MDN, JavaScript Reference, Object.defineProperty()