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
- Object.create()
- 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
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.
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!.
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);
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
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' }
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;
// }
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
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
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
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());
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 )
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.
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
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
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
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
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
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.
Top comments (2)
Hi everyone, do you want another series on Javascript - behind the scenes? It would include topics like
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.