When I first switched from Ruby to JavaScript, I found it challenging to grasp the concept of object prototype and the prototypal chain. The class
keyword introduced in ES2015 is simply a syntactical sugar and does not make it easier to understand.
Yet regardless prototype-based or class-based, the reason to have chain or method resolution order (in Python) is to establish connections among classes and better use the inheritance relationship.
When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property called prototype
object, which acts as a template object that it inherits methods and properties from another object (like the __mro__
in Python). That prototype object has a prototype of its own, and so on until an object is reached with null
as its prototype. By definition, null
has no prototype, and acts as the final link in this prototype chain. Before null
is reached the second to last stop should be Object
, as nearly all objects in JavaScript are instances of Object
.
How is this working? Well, let’s look at an example.
let f = function () {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a: 1, b: 2}f.prototype.b = 3;
f.prototype.c = 4;
Right now:
— o.[[Prototype]]
has properties b and c, and
— o.[[Prototype]].[[Prototype]]
is Object.prototype, and
— o.[[Prototype]].[[Prototype]].[[Prototype]]
is null.
So the whole property chain looks like:
{a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
Note that {a: 1, b: 2}
are object f’s own properties that are not inherited from anywhere else.
When we try to access o.b
we get value 2
. Why not 3
?Because .b
exists as f
own property, and if it is defined, then the value is read and the prototype chain walk stops here. Even the prototype also has a b
property, but it's not visited. This is called Property Shadowing.
Let’s look at a more concrete example on walking up the chain of prototypes.
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";console.log(doSomeInstancing.prop); //some value
console.log(doSomeInstancing.foo); // bar
console.log(doSomething.prop); // undefined
console.log(doSomething.foo); // undefined
console.log(doSomething.prototype.prop);// undefined
console.log(doSomething.prototype.foo); // bar
As seen above, the __proto__
of doSomeInstancing
is doSomething.prototype
.
When you access a property of doSomeInstancing
, the browser first looks to see if doSomeInstancing
has that property.
If doSomeInstancing
does not have the property, then the browser looks for the property in the __proto__
of doSomeInstancing
(a.k.a. doSomething.prototype
).
If the __proto__
of doSomeInstancing does not have the property, then the browser looks for __proto__
of the __proto__
of doSomeInstancing, in this case window.Object.prototype
.
If the property is not found, then the __proto__
of the __proto__
of the __proto__
of doSomeInstancing is looked through, in which case null
.
But __proto__
does not exist on null. So the result undefined is returned.
In short, prototype
is a property of a Function object. It is the prototype of objects constructed by that function.
__proto__
is internal property of an object, pointing to its prototype.
function example(x, y) {
this.x = x;
this.y = y;
}
When JavaScript executes this code, it adds prototype
property to example
, prototype
property is an object with two properties to it:
constructor
__proto__
So when we do example.prototype
it returns
constructor: example(x,y)
__proto__: Object
Now as you can see constructor
is nothing but the function example
itself and __proto__
points to the root level Object
of JavaScript.
var myExample = new example();
In above line we create an instance of example
. How it works?
- It first creates an empty new object
{}
- It creates
__proto__
onmyExample
and makes it point toexample.prototype
somyExample.__proto__ === example.prototype
- It executes
example.prototype.constructor
(which is definition of functionexample
) with the newly created empty object as its context (this
), so thex,y
property gets added to newly created object. - It returns newly created object
// the following are all true
myExample.__proto__ == example.prototype
myExample.__proto__.__proto__ == Object.prototype
myExample.__proto__.__proto__.__proto__ == null
myExample instanceof example;
myExample instanceof Object;
There are different ways to create create objects and the resulting prototype chain.
- Objects created with syntax constructs
var o = {a: 1};
- With a constructor
A “constructor” in JavaScript is “just” a function that happens to be called with the new operator.
function Rectangle() {
this.length = 2;
this.width = 3;
}Rectangle.prototype = {
area: function() {
this.length * this.width;
}
};var re = new Rectangle();
// re is an object with own properties 'length' and 'width'.
// re.[[Prototype]] is the value of Rectangle.prototype when new Rectangle() is executed.
- With
Object.create
ECMAScript 5 introduced a new method: Object.create()
. Calling this method creates a new object. The prototype of this object is the first argument of the function:
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)
- With the
class
keyword
ECMAScript 2015 introduced a new set of keywords implementing classes. The new keywords include class
, constructor
, static
, extends
, and super
.
Be careful with accessing or modifying prototype
.
Note that the __proto__
is considered depreciated. You should use the following methods if possible.
Object.create(proto, [descriptors])
— creates an empty object with givenproto
as[[Prototype]]
and optional property descriptors.Object.getPrototypeOf(obj)
— returns the[[Prototype]]
ofobj
.Object.setPrototypeOf(obj, proto)
— sets the[[Prototype]]
ofobj
toproto.
But don’t modify the prototype
of an object on the fly if possible as it should only be generated when the object is created. JavaScript engines are highly optimised for this. Changing the prototype
in the later stage will break the internal optimisations for object property access operations.
That’s pretty much of it today!
Happy Reading!