Skip to main content

Object & Prototypal Inheritance

Objects

In JavaScript, any value that is not a String, a Number, a Symbol or true, false, null or undefined is an Object. Objects penetrate almost every aspect of this language, thus it's really important to learn the objects first before going anywhere else in-depth. Even though Strings, Number and Boolan are not objects, but they behave like immutable objects.

An object is an unordered collection of properties, each of which has a name and a value.

A JavaScript object also inherits the properties of another object, known as its "Prototype". The methods of an object are typically inherited properties, and this "Prototype inheritance" is a key feature of JavaScript.

It is sometimes important to be able to distinguish between properties defined directly on an object and those that are inherited from a Prototype object. JavaScript uses the term own property to refer to non-inherited properties.

In addition to its name and value, each property has three property attributes:

  • The writable attribute specifies whether the value of the property can be set.
  • The enumerable attribute specifies whether the property name is returned by a for...in loop.
  • The configurable attribute specifies whether the property can be deleted and whether its attribute can be altered.

Creating objects

There are three ways we can create an object in JavaScript, with object literals, with new keyword and with the Object.create() method.

Creating objects with object literals

The easiest way we can create an object is by including object literal. It is a comma-separated list of colon-separated name:value pairs, enclosed within curly braces.

Let's take an example:

const user = { name: "John Doe", address: "localhost" };
// An object `user` with two string properties

Creating objects with new keyword

The new operator creates and initializes a new object. The new keyword must be followed by a function invocation. A function used in this way is called a constructor and initialize the newly created object.

Let's take an example:

function User(name, address) {
this.name = name;
this.address = address;
}

const john = new User("John Doe", "localhost:3000");
// An object `john` is created with two string propertes.

// Or

class Emloyee {
constructor(name, address) {
this.name = name;
this.address = address;
}
}

const bob = new Employee("Bob", "localhost:8080");
// An object `bob` is created with two string properties.

Prototype

Before approaching the 3rd process of creating objects with the Object.create() function, we have to pause for a moment to understand what are __proto__ and "Prototype" in JavaScript? You might not have noticed, but in JavaScript, all objects have a hidden [[prototype]] property that is either an object or null.

proto & Protoype

As you can see the john object has an object __proto__ and also the User() function has a "Prototype" object, these are automatically generated by the JavaScript engine.

So almost every JavaScript object has a second JavaScript object associated with it. This second object is known as [[prototype]] and the first object inherits properties from the prototype.

We already know that which is not primitive are objects in JavaScript. So, the functions are objects and objects are the object. And every object has a second object known as [[prototype]].

Do not confuse yourself with __proto__ with [[prototype]]. __proto__ is the getter/setter for the [[prototype]] object.

const cat = {
canEat: true,
canSleep: true,
};

const mew = {
canSing: true,
__proto__: cat, // setiing cat as the prototype of mew.
};

console.log(mew.canEat); // true
console.log(mew.canSing); // true

The capability of extending objects was there from the very beginning of the language. That's why JavaScript is known as the prototype-based functional object-oriented programming language.

Let's keep aside the concepts of object-oriented programming, and let's look at the above example.

console.log(cat.isPrototypeOf(mew)); // true

console.log(mew.__proto__ === cat); // true
console.log(cat.__proto__ === Object.prototype); // true
console.log(mew.__proto__.__proto__ === Object.prototype); // true
console.log(mew.__proto__.__proto__.__proto__ === null); // true

protoype chain

The above example creates a prototype chain that goes up to Object.prototype. It is also an object but it didn't have its own [[prototype]], the prototype chain ends here. We learned that every object in JavaScript has a second object known as [[prototype]] but it is an exception for Object.prototype it didn't have the second object it points to null.

prototype chain

So, in short [[prototype]] is nothing but a mechanism to extend objects. Traditionally we extend object using classes, in ES6 JavaScript do support classes but under the hood, it is the [[prototype]] object that extends the child object. That's why it is important to know what is the prototype in JavaScript, it is a key feature of the language.

More on Objects

For all objects in addition to its name and value, each property has three property attributes:

  • writable
  • enumerable, and
  • configuable

When we create objects either with object literals or with a new operator, the JavaScript engine sets true default values to these attributes for each property. In Object.prototype there is a method to check the values of these property attributes that is Object.getOwnPropertyDescriptors() it returns an object containing all own property descriptors of an object. These three attributes are known as property descriptors.

const user = {
name: "John Doe",
address: "California",
};

console.log(Object.getOwnPropertyDescriptors(user));
Output
{
name: {
value: 'John Doe',
writable: true,
enumerable: true,
configurable: true
},
address: {
value: 'California',
writable: true,
enumerable: true,
configurable: true
}
}

Creating objects with Object.create()

When creating objects with the Object.create() method, we have the freedom to set property descriptors according to our needs. The function receives two arguments:

  • [[prototype]] - sets the prototype of the newly created object.
  • property descriptors - sets the property descriptors for the newly created object.
const human = Object.create(Object.prototype, {
eye: {
value: 2,
writable: false, // property cannot be set
enumerable: true, // property name can be returned by for..in loop
configurable: false, // property attributes cannot be altered
},
nose: {
value: 1,
writable: false, // property cannot be set
enumerable: true, // property name can be returned by for..in loop
configurable: false, // property attributes cannot be altered
},
leg: {
value: 2,
writable: true, // property can be set
enumerable: true, // property name can be returned by for..in loop
configurable: true, // property attributes cannot be altered
},
name: {
value: "Homo sapiens",
writable: false, // property cannot be set
enumerable: false, // property name cannot be returned by for..in loop
configurable: false, // property attributes cannot be altered
},
});

console.log(human.eye);

The above example creates an object human with the following properties eye, nose, leg, name in such a manner that the value of eye cannot be updated, name cannot be returned in for..in loop. We have created the object according to our needs further modification is not allowed except for the leg property. Since all JavaScript objects has a second object [[prototype]] the human object [[prototype]] set to Object.prototype object.

Let's try to modify human object.

huamn.eye = 3; //Cannot assign to read only property 'eye' of object
console.log(human.eye);

With the help of the writable:false property descriptor, we turned the eye property of the human object into a read-only property.

In the following example, we have created another object john that inherit the human object. The modern way of setting [[prototype]] is using the Object.setPrototypeOf() method. The old way of setting [[prototype]] using __proto__ is deprecated on the browser side. It is available on the server-side (nodejs) for just historical reasons.

The below example lists all the properties of the john object has. We have declared only one property of the john object rest four of them are inherited from the human object.

const john = {
canCode: true,
};

Object.setPrototypeOf(john, human);

for (const key in john) {
console.log(`${key} -> ${john[key]}`);
}
Output
canCode -> true
eye -> 2
nose -> 1
leg -> 2

john object has 5 properties but it only shows up 4, why? Because we set the enumerable flag to false at the parent object human. Thus it did not return in the for..in loop.

Configuring object

Property descriptors work at the level of individual properties. There are also methods available at the Object.prototype object that limit access to the whole object.

  • Object.preventExtension(obj)
    • Forbids the adding of new properties to the object.
  • Object.seal(obj)
    • Forbids adding/removing/customizing of the properies
    • flag cofigurable: false to all existing properties
  • Object.freeze(obj)
    • Forbids adding/removing/customizing of the properies
    • flag writable: false to all existing properties
    • flag cofigurable: false to all existing properties

For testing object configuration there are also methods available at the Object.prototype object:

  • Object.isExtensible(obj)
    • Return true, if adding properties is forbiden, else return false otherwise true.
  • Object.isSealed(obj)
    • Returns true if the configurable: false flag set to false, for all properties i.e. if adding/removing/customizing of the properies are forbiden else return false otherwise true.
  • Object.isFrozen(obj)
    • Return true if the writable: false and configurable: false flags are set to false, for all properties if adding/removing/customizing of the properies are forbiden else return false otherwise true.

Resources

References

Articles

Videos