Introduction
JavaScript objects are key-value pairs. These are also known as properties. These properties in turn have different attributes which define the behavior of these properties ([[...]]). In this article, we'll see how to control these attributes and see how we can achieve advanced patterns in JavaScript.
Detecting properties
Before moving forward let's see how we can detect properties:
var hero = {
title: "superman",
evil: false
}
if ( hero.title ) { // this will be false in case of falsey value.
// do some logic
}
if (hero.evil) {
// it will never reach this code block
}
The above code is not the correct way if we want to identify if a current property exists or not. Rather we can use the in operator
var hero = {
title: "superman",
evil: false
}
if("title" in hero) {
// works fine
}
if("evil" in hero) {
// works fine
}
if("toString" in hero){ // prototype properties are also detected
// also works
}
toString is method which is attached to every object by default therefore the in operator might not be a wise choice here instead we can use hasOwnProperty()
var hero = {
title: "superman",
evil: false
}
if(hero.hasOwnProperty("title")) {
// works fine
}
if(hero.hasOwnProperty("evil")) {
// works fine
}
if(hero.hasOwnProperty("toString")){
// never reaches here as expected
}
Object.keys(hero) is also a good way to detect own properties of an object.
Internal Properties vs Property Descriptors
Both of them are denoted by [[...]]. Internal properties define how the JavaScript engine behaves when code is executed. Property descriptors only define how a property inside an object should behave.
Below here are some common internal properties.
Internal Property | Info |
[[Call]] | This is called when a function |
is invoked. |
| [[Delete]] | this is called when using the
delete keyword on a object property. |
| [[Extensible]] | this is determines whether a new
property can be added to the object or not. |
| [[Prototype]] | contains the prototype object. |
| [[Scope]] | contains the scope of context (closure)
for a particular function |
| [[Construct]] | Same as [[Call]] but called
when a constructor is invoked. |
Property Descriptors
Property descriptors are of two types data properties and accessor properties. Data properties have [[Value]], while accessor properties have [[Get]] and [[Set]]. [[Configurable]] and [[Enumerable]] are present in both types of properties. Read more about accessor properties(getters and setters) vs data properties here.
Property attributes | Info |
[[Configurable]] | specifies if the property can be |
redefined or delete operator can be used.
Also, defineProperty will fail for
the second time if the configuration is false |
| [[Enumerable]] | specifies if the property will be
returned by the for ... in loop |
| [[Writable]] | specifies if the property value can be updated. |
| [[Value]] | contains the actual value of the property. |
| [[Get]] | This function is called
when get accessor is called |
| [[Set]] | This function is called when
set accessor is called |
When we create an object using the usual way, [[Enumerable]], [[Writable]] are set to true, and [[Value]], [[Get]], [[Set]] is defined as whatever we have assigned it to.
var hero = {
_power: 10,
title: "superman",
get power: function() {
return 100+this._power
}
}
/* "title" property descriptors
[[Enumerable]] -> true
[[Writable]] -> true
[[value]] -> "superman"
*/
/* "power" property descriptors
[[Enumerable]] -> true
[[Writable]] -> true
[[Get]] -> function () { return 100 + this._power }
*/
We can also change a property's descriptor using Object.defineProperty( obj, config ).
var hero = {
_title: "Superman",
get title() {
console.log("I am the real " + this._title);
return this._title;
},
set title(value) {
console.log("Setting title to %s", value);
this._title = value;
}
};
The above code is exactly like the one below var hero = { _title: "Superman" }; Object.defineProperty(hero, "title", { get: function() { console.log("I am the real " + this._title); return this._title; }, set: function(value) { console.log("Setting title to %s", value); this._title = value; }, enumerable: true, configurable: true });
We can also define multiple properties at one go using Object.defineProperties(obj, config)
var hero = {};
Object.defineProperties(hero, {
// data property to store data
_title: {
value: "Superman",
enumerable: true,
configurable: true,
writable: true
},
// accessor property
title: {
get: function() {
console.log("I am the real " + this._title);
return this._title;
},
set: function(value) {
console.log("Setting title to %s", value);
this._title = value;
},
enumerable: true,
configurable: true
}
});
Getting property descriptors
var character = {
firstName: 'Clark',
lastName: 'Kent'
};
let descriptor = Object.getOwnPropertyDescriptor(character, 'firstName');
console.log(descriptor);
Output { value: 'Clark', writable: true, enumerable: true, configurable: true }
preventExtension, freeze, seal
preventExtension sets the [[Extensible]] property of object to false. Therefore no new property can be added to the object.
seal set the [[Extensible]] property to false and also the [[Configurable]] descriptor of all the properties of the object to false.
freeze this sets every descriptor of all properties to false and also makes the object non-extensible. This is used to have a snapshot of the object
var hero = { title: "superman" }
Object.preventExtension(hero)
console.log(Object.isExtensible(hero)) //false
hero.power = 100 // Error: Can't add new property
var hero = { title: "superman" }
Object.seal(hero)
console.log(Object.isSealed(hero)) //true
delete hero.title // Error: Can't remove property
var hero = { title: "superman" }
Object.freeze(hero)
console.log(Object.isFreezed(hero)) //true
hero.title = "Batman" // Error: Can't reassign value
Using all this concepts we can replicate Object Oriented Programming like in Java, which I'll explain in depth in my next article.