DEV Community

Cover image for ✅ JavaScript Inheritance without classes
angDecoder
angDecoder

Posted on

✅ JavaScript Inheritance without classes

In previous tutorial I mentioned the [[Prototype]] chain several times, but haven’t said what exactly it is. We will now examine prototypes in detail.
Before starting with this tutorial please do not have any preconceived ideas about any topic as the keywords might be similar to other languages but their meaning might be completely different in JavaScript.

Table Of Contents

1. What is Prototype?
2. [[Prototype]] links
3. proto
4. '.constructor' Property
5. Getters and Setters
6. [[GET]] Operation
7. [[PUT]] Operation


1. What is Prototype?

Prototype is a mechanism by which JavaScript objects inherit features from one another.

But when it comes to inheritance JavaScript unlike other Object-oriented languages like C++ or Java doesn't have classes, it only has objects. So, unlike other languages JavaScript uses objects for inheritance.

At this point you might point out that ES6 introduced Class into JavaScript, but that is just a syntatic sugar provided for ease of use. JavaScript do not have any concept of Class just introducing a keyword doesn't change that. Under the hood it is all just objects and constructor functions.


2. [[Prototype]] links

Objects inherit features from another objects but how do they access these features ?
The answer is [[Prototype]] - it is a hidden private property of objects which references other objects. An object's prototype is the object which it descends from or inherits from.

There are generally two ways of creating [[Prototypes]] links

  1. Object.create()
  2. new Operator

2.1 Object.create()

Object.create() takes objects as argument and returns another object which is [[Protype]] linked to the former object.

var obj1 = {
    a : 5,
    name : 'obj1'
};

var obj2 = Object.create(obj1);
obj2.name = 'obj2';

console.log(obj2.a); // 5
console.log(obj2); // { name : 'obj2' } -> doesn't have 'a' property
Enter fullscreen mode Exit fullscreen mode

In the above example, the obj2 was able to access property a even though it does not own the property. This is because obj2 is [[Prototype]] linked to obj1.

Let us now see what the last line prints in the console. If you look closely you will see it has a dropdown arrow besides it. Using it we will inspect the obj2 object.

Inspect the object

In the above image you can see [[Prototype]] linkage of obj2. Also notice that the obj1 object is [[Prototype]] which points at the native Object() contructor. It contains all the default function provided to objects which can be accessed via the prototypal inheritance.
Can we create objects which are not linked to the native Object() contructor?
The answer is 'YES' by passing 'null' to Object.create(). This object is usually called dictionary. Below I have just written JavaScript directly in my browser console. Yes, you can do that!.

Dictionary object

2.2 new Operator

Every function in JavaScript has data property called prototype. When function is called with new operator this prototype is set as the prototype of the newly created object. Let us demonstrate the working with an example :-

function fun(){
    this.a = 4;
}

var obj = new fun();

console.log(obj);
console.log(fun.prototype);
Enter fullscreen mode Exit fullscreen mode

Displaying prototype

Here you can see the fun.prototype is same as [[Prototype]] of obj.


3. __proto__

It is commonly called dunder proto or double under proto. It is a property of Object.prototype which exposes the hidden [[Prototype]] property of an object. It can be used to modify object's [[Prototype]] link.

var obj1 = {
    name : 'obj1'
};

var obj2 = Object.create(obj1);
obj2.name = 'obj2';

console.log(obj2.__proto__===obj1); // true
console.log(obj2.__proto__===Object.prototype); // true
Enter fullscreen mode Exit fullscreen mode

Since, we can manually change the [[Prototype]] links using the __proto__ property, care must be taken so that no cycles are formed.

The use __proto__ property has now depreciated and these properties are used instead :-

var obj1 = {
    name : 'obj1'
};
var obj2 = {
    name : 'obj2'
}

// earlier obj1.__proto__ = obj2
Object.setPrototypeOf(obj1,obj2); 

// earlier obj1.__proto__
console.log( Object.getPrototypeOf(obj1) ); // { name : 'obj2' }
Enter fullscreen mode Exit fullscreen mode

4. '.constructor' Property

Just the name is enough to confuse people, because there are no constructors in JavaScript. In fact there are no classes in JavaScript, so need of the constructor. It can be pretty misleading for example consider the following code :

function fun(){
    this.a = 5;
};

var obj = new fun();

console.log( obj ) // { a : 5 }
console.log(obj.constructor);
// ƒ fun(){
//     this.a = 5;
// }
Enter fullscreen mode Exit fullscreen mode

It seems that the obj was constructed with the help of function fun so it has a property constructor stating that it was constructed by fun.But if you look closely obj doesn't have constructor property.
So, where did it come from?
The answer is fun.prototype, if we inspect the function fun.prototype we would find that it has a property constructor. The object obj was only able to access it because it was [[Prototype]] linked to fun.prototype.

function fun(){
    this.a = 5;
};

var obj = new fun();

console.log(obj.hasOwnProperty('constructor')); // false
console.log(fun.prototype.hasOwnProperty('constructor')); //true
Enter fullscreen mode Exit fullscreen mode

So, what is the problem can't we still find that obj was constructed using the fun function? Yes, that is only true for the mean time until anyone changes [[Prototype]] link, then everything falls apart.

function fun1(){}
function fun2(){}
var obj = new fun1();

console.log( obj.constructor ); // fun1

Object.setPrototypeOf(obj1,fun2.prototype);
console.log(obj.constructor); // fun2 -> it got changed
Enter fullscreen mode Exit fullscreen mode

Therefore, obj.constructor does not point to the function that constructed it. Also remember that the constructor is not a property of object but of the function.

NOTE :- Even if obj.constructor = fun1, 
it is not safe to assume that obj was constructed by fun1
Enter fullscreen mode Exit fullscreen mode

5. Getters and Setters

In JavaScript, accessor properties are methods that get or set the value of an object. For that, we use these two keywords:

  • get : to define a getter method to get the property value
  • set : to define a setter method to set the property value

The following are two ways of declaring the getters and setters :-

// FIRST METHOD -> OBJECT LITERAL METHOD
var obj1 = {
    name : "Peter Parker",
    get getName(){
        return this.name;
    },
    set changeName(n){
        this.name = n;
    }
};
console.log(obj1.getName()); 

// SECOND METHOD -> Object.defineProperty();
var obj2 = { name : 'Bruce Wayne' };
Object.defineProperties(obj2,'getName',{
    get : function(){
        return this.name;
    }
});

Object.defineProperty(obj2,'changeName',{
    set : function(n){
        this.name = n
    }
})
console.log(obj2.getName());
Enter fullscreen mode Exit fullscreen mode

6. [[GET]] Operation

When we try to access properties on any object then [[GET]] operation is performed to retrieve the value. When we access the property on object, it firsts checks if the object contains the property or not. If it contains the property then it simply returns the value. If the requested property is not found in the object itself, JavaScript will traverse up the prototype chain until it finds the property or returns undefined if it doesn't exist.

So, where do you think this chain ends?
JavaScript traverses the [[Prototype]] chain until the [[Prototype]] of certain object in chain points at null. For the most cases the last object is Object.prototype but if object is created using Object.create(null) then it's [[Prototype]] points at null.

var obj1 = {
    a : 4
};

var obj2 = Object.create(obj1);
var obj3 = Object.create(obj2);

console.log(obj3.a); // 4 
// obj3 -> obj2 -> obj1 ( property found returns value )

console.log(obj3.b) // undefined
// obj3 -> obj2 -> obj1 -> Object.prototype -> null 
// ( property not found returns undefined )
Enter fullscreen mode Exit fullscreen mode

7. [[PUT]] Operation

[[GET]] operation was easy right, well [[PUT]] operation is where things get really interesting. [[PUT]] operation is performed when we JavaScript encounters assignment to a object property. For eg:- obj.a = 5.

There arises various scenarios :-

1. The object already has the property.

2. The object doesn't have the property but 
 some object in [[Prototype]] chain has the property 
   a. The property is writable false 
   b. The property is writable true
   c. If the setter is present

3. Neither the object nor other object in 
 [[Prototype]] link has property. 
Enter fullscreen mode Exit fullscreen mode

Let us resolve all these scenarios one by one :-

1. If the object already has the property

In this case the property would be updated without any further consideration.

var obj1 = {
    name : 'obj1'
};

var obj2 = { name : 'obj2' };
Object.setPrototypeOf(obj1,obj2); // obj2 [[Prototype]] linked to obj1 

obj2.name = 'obj2 changed';

console.log(obj2.name,obj1.name);// 'obj2 changed', 'obj1'
// the name property of obj2 was successfully updated
// without ever changing the value of obj1
Enter fullscreen mode Exit fullscreen mode

2. The object doesn't have the property but some object in [[Prototype]] chain has the property.

This is a tricky scenario because it there are further 3 possibility :-

a. The property is writable false

In this case neither the property would be updated nor new property is added to the object.

var obj1 = {};

Object.defineProperty(obj1,'name',{
    value : 'obj1',
    writable : false
});

var obj2 = Object.create(obj1);
obj2.name = 'obj2';

console.log(obj1.name,obj2.name); // 'obj1', 'obj1`
console.log( obj2.hasOwnProperty('name') ); // 'name' property not added to obj2

// NOTE :- 'name' property was not added to obj1 it 
// was only able to access this property via the [Prototype]] link
// The 'name' property in obj1 remained unchanged
Enter fullscreen mode Exit fullscreen mode

b. The property is writable true

In this case, property would be added to the current object which would shadow the other object.
Shadowing of property :- When an object has property name same as another object higher up in it's [[Prototype]] chain.

var obj1 = {
    name : 'obj1' // by default writable true
};

var obj2 = Object.create(obj1);
obj2.name = 'obj2';

console.log(obj1.name,obj2.name); // 'obj1', 'obj2`
console.log( obj2.hasOwnProperty('name') ); // 'name' property added to obj2

// NOTE :- The obj1 object shadows obj2 
// the value of property in obj1 is not changed
Enter fullscreen mode Exit fullscreen mode

c. Setter is present

In this case, simply the setter is called.

var obj1 = {
    name : 'obj1',
    set changename(n){
        console.log('setter called');
        this.name = n;
    }
};

var obj2 = Object.create(obj1);
obj2.changename = 'obj2'; // setter called

console.log(obj1.name,obj2.name); // obj1, obj2

// NOTE :- Setter is called during the assignment
// the properties in obj1 remains unchanged
Enter fullscreen mode Exit fullscreen mode

d. Neither the object nor other object in [[Prototype]] link has property.

In this case, simply the property is added to current object.

var obj1 = {
    a : 4
};

var obj2 = Object.create(obj1);
obj1.b = 5;

console.log( obj1.hasOwnProperty('b') ); // true
console.log( obj1.b,obj2.b ); // undefined, 5

// NOTE :- Property added to obj2
// obj1.b gives undefined as obj1 doesn't have 'b' property
Enter fullscreen mode Exit fullscreen mode

This post marks the end of this 3 part series where I have tried to explain how objects work in JavaScript. This was a very wonderful experience connecting with new people.

Also, you can connect with me on linkedin and Github

Top comments (2)

Collapse
 
angdecoder profile image
angDecoder

Hi everyone, do you want another series on Javascript - behind the scenes? It would include topics like

  1. Execution context
  2. Hoisting
  3. Types of function and its working
  4. Scope chain
  5. Let vs const vs var And some other cocepts
Collapse
 
druchan profile image
druchan • Edited

Insightful!

I think coverage of Proxies could also benefit as that seems to be a more robust way of creating instances of another object with more granular control over the actions on an object without affecting the original prototype.