Dissecting objects in JavaScript

Dissecting objects in JavaScript

Know your JS

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 PropertyInfo
[[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 attributesInfo
[[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

  1. preventExtension sets the [[Extensible]] property of object to false. Therefore no new property can be added to the object.

  2. seal set the [[Extensible]] property to false and also the [[Configurable]] descriptor of all the properties of the object to false.

  3. 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.