JavaScript series: Prototype-based inheritance
JavaScript does not have classes so when we’re talking about prototypes try not to think about classes. This can be very confusing for developers who come from a class oriented language like C# or java. JavaScript is truly an object oriented language. All objects in JavaScript are descended from the “root object” called Object
. The prototype itself is an object that exists as a property on all objects and it’s what’s used to implement prototype-based inheritance and shared properties. By default a newly created object has a prototype that is a reference to Object.prototype
.
The prototype chain
Let’s create a new object.
var obj = { myFunction() { console.log(1); } }; console.log(obj); // { myFunction: [Function: myFunction] } console.log(obj.toString()) // [object Object] console.log(obj.hasOwnProperty("myFunction")); // true console.log(obj.hasOwnProperty("toString")); // false
First we created an object called obj
with the object literal syntax. When we log the object to the console we can see that it has a function named myFunction
. But somehow obj.toString()
references a function that prints [object Object]
. I’ve then used a function called hasOwnProperty
which is also not defined on obj
which prints true for myFunction
but prints false for hasOwnProperty
. So where does these functions come from now that we can that they’re clearly not defined on obj
.
This is where the JavaScript prototype object comes in. When calling obj.toString()
and obj
does not have that property it will try and go to it’s prototype and look for it. In our case obj’s prototype will be a reference to Object.prototype
which is the default for created objects. When it came to Object.prototype
it found a property named toString and used that. This is how the prototype works. JavaScript will traverse up the prototype chain until it finds the property it’s looking for or if the prototype is null
which the prototype of Object.prototype
is.
So what do the hasOwnProperty()
function tell us. This description is from developer.mozilla.org
Returns a boolean indicating whether an object contains the specified property as a direct property of that object and not inherited through the prototype chain.
This means that obj doesn’t own the toString
function and is only accessible because of how the prototype works.
The new keyword and creating a prototype
var obj1 = { aFunction: function() { return 1; }, aProperty: 123, } function Obj2() { } Obj2.prototype = obj1 // Assign obj1 as the prototype of Obj2 var obj2 = new Obj2(); // Create a new empty object and set it's prototype to reference Obj2.prototype console.log(obj2); // {} console.log(obj2.aFunction()); // 1 console.log(obj2.aProperty); // 123 console.log(Object.getPrototypeOf(obj2) === Obj2.prototype); // true console.log(Object.getPrototypeOf(obj2) === obj1); // true console.log(Object.getPrototypeOf(obj2).__proto__ === Object.prototype); // true
In the above code we created an object called obj1
with a function and a property with an integer value. Then we created a function object called Obj2
and sat it’s prototype to reference object obj1
. On line fourteen we used the new
keyword in front of the Obj2
function call. This is where it can get confusing coming from a class oriented language. When the new
keyword is put in front of a function call it creates an empty object and assigns it to the this
keyword it then magically returns the new object. In this case it sets it’s internal prototype reference to Obj2.prototype
which in turn references the obj1
object. The obj1
objects internal prototype references Object.prototype
. So now we have access to properties and functions in both object obj1
and Object.prototype
through the prototype chain.
The Object.getPrototypeOf
was standardized in ECMAScript 5 and gets the prototype of an object. The __proto__
property pronounced “dunder proto” is standardized in ECMAScript 6 but exists in most browsers today but it’s behavior is not standardized in earlier versions of ECMAScript. If you only want to read the prototype, Object.getPrototypeOf
and __proto__
achieves the same thing.
It’s important to understand that JavaScript does not make copies of prototypes when a new object is created. Prototypes are only references to other objects.
function Obj1() { this.sayHello = function() { console.log("Obj1 said hello"); } } Obj1.prototype.sayHello = function() { console.log("Obj1 prototype said hello"); } var obj1 = new Obj1(); obj1.sayHello(); // Obj1 said hello Object.getPrototypeOf(obj1).sayHello(); // Obj1 prototype said hello console.log(Object.getPrototypeOf(obj1) === Obj1.prototype) // true console.log(obj1.hasOwnProperty("sayHello")); // true
First let’s repeat again what the new
keyword does. It creates an empty object and assigns it to the this
keyword. The prototype of the newly created object gets linked to the prototype of the function which was called with new keyword in front of it and then returns the object. Here we created a function called sayHello
on the newly created object which is then assigned to the object called a
. We also created a function called sayHello
on the prototype object of A
. What’s happening now when we call a.sayHello()
is that it finds a function named sayHello
directly on object a
and will therefore not traverse the prototype chain. This is called shadowing. We can still access our prototypes version of sayHello by referencing the prototype object.
Object.create vs new
The object.create
function is available since ES5. This is what the ECMAScript specification says about the Object.create function:
Object.create ( O [ , Properties ] )
The create function creates a new object with a specified prototype
function Obj1() { this.sayHello = function() { console.log("Obj1 said hello"); } } Obj1.prototype.sayHello = function() { console.log("Obj1 prototype said hello"); } var obj1WithNew = new Obj1(); var obj1WithCreate = Object.create(Obj1.prototype); obj1WithNew.sayHello(); // Obj1 said hello console.log(obj1WithNew.hasOwnProperty("sayHello")); // true obj1WithCreate.sayHello(); // Obj1 prototype said hello console.log(obj1WithCreate.hasOwnProperty("sayHello")); // false
Here we can see that when using the new
keyword we always have to run the constructor call to create a new object. But with Object.create
we get an empty object without making a constructor call. The object1WithCreate
object does not have a property named sayHello
which the ECMAScript specification explained above. To further explain Object.create
let’s look at this simple polyfill without the second parameter.
Object.create = function(o) { function F() {} F.prototype = o; return new F(); };
The polyfill creates an empty object with a constructor call that does nothing and assigns the prototype
to the passed in object before returning it.
As we can see Object.create
makes inheritance in JavaScript simpler.
Conclusion(tl;dr)
JavaScript is an object-oriented language with prototype-based inheritance. When a property is not found on the current object it will traverse the prototype chain until it finds the property or reaches the top(when that prototype objects prototype is null).
The new
keyword can be placed in front of any function call which makes JavaScript create an empty object and sets it’s prototype to reference the called functions prototype.
Shadowing is when a property is specified further down the prototype chain which hides a property with the same name higher up.
Object.create
is used to create an empty object with a specified prototype without making a constructor call.