Javascript Property Descriptors allow us to configure a JavaScript Property using the flags like enumerable, writable & configurable. We can read the Property Descriptors using the getOwnPropertyDescriptor
and alter them using the DefineProperty
.
Table of Contents
What is Property Descriptors
Property Descriptors hold the configurations of an object’s property.
We can create a JavaScript object in many ways. Every property that we add to the object automatically gets its own Property descriptor. A property descriptor is a simple object and contains the metadata or information about the Property.
There are two types of Properties in JavaScript. Data Properties & Accessor Properties.
Data Properties maps to a value (i.e. primitive value, object, or function). Accessor Properties maps to getter & Setter methods. Each of these Property types also has its own Property Descriptors. Hence are two types of Property Descriptors.
- Data Descriptors ( for Data Properties)
- Accessor Descriptors (for getter & setter or Accessor Properties)
A JavaScript Property can have only one of the Descriptors. It cannot have both Descriptors.
Data Descriptors
Data Descriptors is for Data Properties. It contains the following fields
- value
- Writable
- Enumerable
- configurable
When we create a new object using the Object literal Syntax or any other method, JavaScript adds the descriptor for each Property. By default, it creates the property Writable
, Enumerable
& configurable
set to true
. The Value
property is set to the value of the Property.
Accessor Descriptors
Accessor Descriptors is for Accessor Properties. It contains the following fields
- set
- get
- Enumerable
- configurable
When we create a getter & setter property using the Object literal Syntax JavaScript adds this descriptor to the property. By default, it creates the descriptor with Enumerable
& configurable
set to true
. The get
& set
mapped to the getter & setter functions.
1 2 3 4 5 6 7 8 9 10 11 | const person = { _name: "", get name() { return this._name; }, set name(value) { this._name = value; }, }; |
getOwnPropertyDescriptor
You can read the Property Descriptor of any property using the getOwnPropertyDescriptor
method.
- It returns the Property Descriptor of the own property i.e. property directly present on the object.
- You can change the object, But it will not have any effect on the original property’s Descriptor.
Syntax
1 2 3 | Object.getOwnPropertyDescriptor(obj, prop) |
Where
obj
is the object in which to look for the property.
prop
name of the property whose description we want to retrieve
The following is a person object with name (accessor property) & age (data property) properties. We use the getOwnPropertyDescriptor
to retrieve their descriptors
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | const person = { _name: "", get name() { return this._name; }, set name(value) { this._name = value; }, age:30 }; console.log(Object.getOwnPropertyDescriptor(person,'name')) console.log(Object.getOwnPropertyDescriptor(person,'age')) |
You can see from the output that the accessor property descriptor does not have writable
& value
property. The data property descriptor does not have get
& set
property
Also, note that configurable
, enumerable
& writable
are set to true
Setting the Descriptors
We can modify the object returned by the getOwnPropertyDescriptor
, but it will not change the descriptor of the original property
To change the descriptor of a Property, we can use the defineProperty
or defineProperties
method
defineProperty
The defineProperty adds a new property or modifies an existing property. It returns the object.
Syntax
1 2 3 | Object.defineProperty(obj, prop, descriptor) |
Where
obj
in which to look for the property.
prop
is the name of the property whose description we want to add or modify.
descriptor
for the property that wants to add or modify
For a new property, If we do not provide any values for Writable
, Enumerable
& configurable
, then they are set to false
. The value
is set to undefined
.
Example
The following code creates an empty person object. We then add the _name
& age
data properties & name
getter & setter method using the defineProperty
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | const person = {}; Object.defineProperty(person, "_name", { value: "", writable: true, configurable: true, enumerable: true, }); Object.defineProperty(person, "name", { get() { return this._name; }, set(value) { this._name = value; }, configurable: true, enumerable: true, }); Object.defineProperty(person, "age", { value: "30", writable: true, configurable: true, enumerable: true, }); |
You can pass an empty descriptor for a Property. In that case and if the property is new then the defineProperty will use the false
as the default value for writable
, enumerable
, & configurable
. The default value for value
property is undefined
.
1 2 3 4 5 6 7 8 9 10 11 12 | const person = {}; Object.defineProperty(person, "age", {}); console.log(person) console.log(Object.getOwnPropertyDescriptor(person,'age')) //{value: undefined, writable: false, enumerable: false, configurable: false} |
But if the property already exists, the existing values are overwritten only if the values are provided.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const person = { age:10 }; console.log(Object.getOwnPropertyDescriptor(person,'age')) //{value: 10, writable: true, enumerable: true, configurable: true} //Does not affect as the descriptoris empty Object.defineProperty(person, "age", {}); console.log(Object.getOwnPropertyDescriptor(person,'age')) //{value: 10, writable: true, enumerable: true, configurable: true} |
defineProperties
It is similar to defineProperty but allows us to add or modify multiple properties
Syntax
1 2 3 | Object.defineProperties(obj, props) |
Where
obj
in which to look for the property.
props
is an object whose each property must be a descriptor (either a data descriptor or an accessor descriptor). The name of the key becomes the property name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const person = {}; obj= { _name : { value: "", writable: true, configurable: true, enumerable: true, }, name : { get() { return this._name; }, set(value) { this._name = value; }, configurable: true, enumerable: true, }, age : { value: "30", writable: true, configurable: true, enumerable: true, } } Object.defineProperties(person,obj); console.log(person) |
Value
This Property of the Property Descriptor holds the Value of the Property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | const person = {}; //Creating new property. note that writable is set true. Object.defineProperty(person, "age", { value:10, writable:true}); console.log(person.age) //10 //No values is provided. does not overwrite Object.defineProperty(person, "age", {}); console.log(person.age) //10 //Sets the age as 30 Object.defineProperty(person, "age", { value:30}); console.log(person.age) //30 person.age=40 console.log(person.age) //40 |
Writable
Writable determines whether you can modify the value of the property or not. A false value makes property read-only.
The following code creates the age property with the value of 10 with writable as false.
Assigning a new value of 20 will fail. If you have use strict
enabled, then JavaScript throws an error else it will fail silently.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | "use strict" const person = {}; //Creating new property. Object.defineProperty(person, "age", { value:10,writable:false, configurable:true,enumerable:true}); console.log(person.age) //10 // person.age=20 // //Cannot assign to read only property 'age' of object '#<Object>' //Remove use strict //Error is thrown only when using use strict. else fails silently console.log(person.age) //10 |
In this example, we revert back the writable:false
to writable:true
. Now you can modify the property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const person = {}; //Creating new property. Object.defineProperty(person, "age", { value:10,writable:false, configurable:true,enumerable:true}); console.log(person.age) //10 person.age=20 //fails console.log(person.age) //10 //Makng Writable true again Object.defineProperty(person, "age", { writable:true}); person.age=20 //works now console.log(person.age) //20 |
Enumerable
An enumerable flag determines if we can view the property using the for..in
loop or Object.keys()
method.
The person object in the following example has three properties. Both for..in
loop & Object.keys()
returns all three because enumerable
by default set to true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | const person = { prop1:"one", prop2:"two", prop3:"three" }; console.log(Object.keys(person)) //["prop1", "prop2", "prop3"] for (const key in person) { console.log(key) } //prop1 //prop2 //prop3 Object.defineProperty(person, "prop2", { enumerable:false}); console.log(Object.keys(person)) //["prop1", "prop3"] for (const key in person) { console.log(key) } //prop1 //prop3 |
We modify the prop2
and set its enumerable
to false. Now both for..in
loop & Object.keys()
will not return prop2
Configurable
Configurable property if true allows us to modify the writable
& enumerable
property of an object. It also allows us to delete the property. But if it is set to false, then you cannot change the value of enumerable
neither you can delete the property. It allows changing the writable from true
to false
but not from false
to true
. Once you set configurable
as false, you cannot set it true
again.
The following code makes the firstName
non-configurable. Now you cannot modify its enumerable
flag.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | "use strict" const person = { firstName:"Bill", lastName:"Gates", }; Object.defineProperty(person, "firstName", { configurable:false}); Object.defineProperty(person, "firstName", { enumerable:false}); //Uncaught TypeError: Cannot redefine property: firstName |
But you can change writable from true to false. But not from false to true.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | "use strict" const person = { firstName:"Bill", lastName:"Gates", }; Object.defineProperty(person, "firstName", { configurable:false}); //No Error here. You can change true to false Object.defineProperty(person, "firstName", { writable:false}); //But cannot make it writable again Object.defineProperty(person, "firstName", { writable:true}); //Uncaught TypeError: Cannot redefine property: firstName |
Non-configurable properties cannot be deleted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | "use strict" const person = { firstName:"Bill", lastName:"Gates", }; Object.defineProperty(person, "firstName", { configurable:false}); delete person.lastName //deleted console.log(person) //cannot delete delete person.firstName //Uncaught TypeError: Cannot delete property 'firstName' of #<Object> |
Setting configurable
to false
is a one-way street. You cannot set it to true
again
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | "use strict" const person = { firstName:"Bill", lastName:"Gates", }; Object.defineProperty(person, "firstName", { configurable:false}); Object.defineProperty(person, "firstName", { configurable:true}); //Uncaught TypeError: Cannot redefine property: firstName |
Get & Set
The Get & Set property descriptors are mapped to the Getter and Setter functions. The getter & setter methods are known as accessor properties in JavaScript. They look like normal properties but are actually functions mapped to a Property.
1 2 3 4 5 6 7 8 9 10 11 12 13 | const person = {}; obj= { _name : { value: "", writable: true, configurable: true, enumerable: true, }, name : { get() { return this._name; }, set(value) { this._name = value; }, configurable: true, enumerable: true, }, } |
Read More