when do you use Object.defineProperty()
I'm wondering when I should use
Object.defineProperty
to create new properties for an object. I'm aware that I'm able to set things like
enumerable: false
but when do you need this really? If you just set a property like
myObject.myprop = 5;
its descriptors are all set to true, right? I'm actually more curious when you guys use that rather verbose call to .defineProperty() and for what reasons.
Solution 1:
Object.defineProperty
is mainly used to set properties with specific property descriptors (e.g. read-only (constants), enumerability (to not show a property in a for (.. in ..)
loop, getters, setters).
"use strict";
var myObj = {}; // Create object
// Set property (+descriptor)
Object.defineProperty(myObj, 'myprop', {
value: 5,
writable: false
});
console.log(myObj.myprop);// 5
myObj.myprop = 1; // In strict mode: TypeError: myObj.myprop is read-only
Example
This method extends the Object
prototype with a property. Only the getter is defined, and the enumerability is set to false
.
Object.defineProperty(Object.prototype, '__CLASS__', {
get: function() {
return Object.prototype.toString.call(this);
},
enumerable: false // = Default
});
Object.keys({}); // []
console.log([].__CLASS__); // "[object Array]"
Solution 2:
Features like 'enumerable' are rarely used in my experience. The major use case is computed properties:
var myObj = {};
myObj.width = 20;
myObj.height = 20;
Object.defineProperty(myObj, 'area', {
get: function() {
return this.width*this.height;
}
});
console.log(myObj.area);
Solution 3:
A really good reason for using Object.defineProperty is that it lets you loop through a function in an object as a computed property, which executes the function instead of returning the function's body.
For example:
var myObj = {};
myObj.width = 20;
myObj.height = 20;
Object.defineProperty(myObj, 'area', {
get: function() {
return this.width*this.height;
},
enumerable: true
});
for (var key in myObj) {
if (myObj.hasOwnProperty(key)) {
console.log(key + " -> " + myObj[key]);
}
}
//width -> 20, height -> 20, area -> 400
Versus adding the function as a property to an object literal:
var myObj = {};
myObj.width = 20;
myObj.height = 20;
myObj.area = function() {
return this.width*this.height;
};
for (var key in myObj) {
if (myObj.hasOwnProperty(key)) {
console.log(key + " -> " + myObj[key]);
}
}
// width -> 20, height -> 20, area -> function() { return this.width*this.height;}
Make sure you set the enumerable property to true in order to loop through it.
Solution 4:
For example, that's how Vue.js keeps track of changes in the data
object:
When you pass a plain JavaScript object to a Vue instance as its
data
option, Vue will walk through all of its properties and convert them togetter/setters
usingObject.defineProperty
. This is an ES5-only and un-shimmable feature, which is why Vue doesn’t support IE8 and below.The getter/setters are invisible to the user, but under the hood they enable Vue to perform dependency-tracking and change-notification when properties are accessed or modified.
[...]
Keep in mind that even a super slim and basic version of Vue.js would use something more than just Object.defineProperty
, but the main functionality comes from it:
Here you can see an article where the author implements a minimal PoC version of something like Vue.js: https://medium.com/js-dojo/understand-vue-reactivity-implementation-step-by-step-599c3d51cd6c
And here a talk (in Spanish) where the speaker builds something similar while explaining reactivity in Vue.js: https://www.youtube.com/watch?v=axXwWU-L7RM
Solution 5:
Summary:
In Javascript Objects are collections of key-value pairs.
Object.defineProperty()
is a function which can define a new property on an object and can set the following attributes of a property:
-
value
<any>
: The value associated with the key -
writable
<boolean>
: if writable is set totrue
The property can be updated by assigning a new value to it. If set tofalse
you can't change the value. -
enumerable
<boolean>
: if enumerable is set totrue
Property can be accessed via afor..in
loop. Furthermore are the only the enumerable property keys returned withObject.keys()
-
configurable
<boolean>
: If configurable is set tofalse
you cannot change change the property attributes (value/writable/enumerable/configurable), also since you cannot change the value you cannot delete it using thedelete
operator.
Example:
let obj = {};
Object.defineProperty(obj, 'prop1', {
value: 1,
writable: false,
enumerable: false,
configurable: false
}); // create a new property (key=prop1, value=1)
Object.defineProperty(obj, 'prop2', {
value: 2,
writable: true,
enumerable: true,
configurable: true
}); // create a new property (key=prop2, value=2)
console.log(obj.prop1, obj.prop2); // both props exists
for(const props in obj) {
console.log(props);
// only logs prop2 because writable is true in prop2 and false in prop1
}
obj.prop1 = 100;
obj.prop2 = 100;
console.log(obj.prop1, obj.prop2);
// only prop2 is changed because prop2 is writable, prop1 is not
delete obj.prop1;
delete obj.prop2;
console.log(obj.prop1, obj.prop2);
// only prop2 is deleted because prop2 is configurable and prop1 is not