Consider an empty object. Print the object using toString
method.
const obj = {}; console.log(obj.toString()); // [object Object]
We get the result [object Object]
in the console. How does it work? (Interview question). Developers who have a strong background in C++ or C# will blindly attribute it to OOPs. All objects inherit from Object.
JavaScript is to Java what Cartoon is to Car.
JavaScript works as designed. But it does not work the same way as C# or Java. Consider the following code.
Object.getOwnPropertyNames(obj); // []
To inspect the properties within an object, JavaScript has getOwnPropertyNames
method. For the empty object, there is no property defined. If obj
were to inherit from Object
, it should have toString
as one of the properties. In JavaScript, objects have only properties. Any function defined within an object is assigned to a property. (I will prove that in a minute). Our empty object has no property of its own. So, where does it get the toString
method? (Can we define method as a property which points to a function?)
Prototype
Every object has a prototype. Prototype is another object. Our empty object has the Object
prototype. When you get a property on an object, JavaScript first checks the own properties of an object. If it is not found there, it looks up to the own properties of the object’s prototype.
To get the object’s prototype, use:
console.log(Object.getPrototypeOf(obj)); console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(obj)));
The first statement in any browser console is misleading. It does not say much about the prototype object. But the second statement clearly shows that the prototype object belongs to Object
.
As you can see from the above screenshot, toString
is found in the prototype object. We can use obj.toString()
and it works as expected.
Function
Object
in JavaScript is a function.
console.log(typeof Object); // function console.log(typeof obj); // object
Functions have a prototype property which points to the prototype object. (Objects do not have prototype property. To get the prototype of an object, use Object.getPrototypeOf
or use __proto__
property).
console.log(Object.getOwnPropertyNames(Object.prototype));
The above statement gives all the properties of the Object’s prototype which we have seen already.
The following three lines of code are equivalent. (Almost, We will leave out the nuances for now.)
const obj = {}; const obj = new Object(); const obj = Object.create(Object.prototype);
An object has properties and linked to a prototype object. Object.create
creates an empty object whose prototype object is specified as the argument. new
is special keyword in JavaScript. It creates a new object whose prototype object is the function’s prototype. So, all three statements do the same thing.
An empty object has a prototype object which is same as the prototype object of Object function
It is possible to create an object with a null prototype as follows.
const reallyEmptyObject = Object.create(null); console.log(reallyEmptyObject.toString); // undefined
As expected, our empty object with null prototype does not have the toString
method defined.
Prototype chain
An object has own properties. But, it is usually linked to a prototype object. A prototype object is also an object. So, the prototype object also has a prototype object and so on. Ultimately, one of the prototype objects will point to a null prototype. To retrieve a property value of an object, JavaScript will check the entire prototype chain till a null prototype is reached.
console.log(Object.getPrototypeOf(Object.prototype)); // null
Armed with the knowledge of object, property access and prototype chain, we are ready to understand the class pattern in JavaScript.
Prototype chain using prototype objects
The first pattern that we will look is quite simple but least used (for various reasons).
We define two prototype objects, Foo
and Bar
. Link them together using Object.setPrototypeOf
. After the linking, Foo
is the prototype object for Bar
. Create a new object with Bar
as the prototype object. The newly created object works as you would expect.
const Foo = { speak: function() { console.log('speak'); } } const Bar = { speakMore: function() { console.log('speak more'); } } Object.setPrototypeOf(Bar, Foo); const bar = Object.create(Bar); bar.speakMore(); // speak more bar.speak(); // speak
To verify, let us print out the own properties of each of the objects.
console.log(Object.getOwnPropertyNames(bar)); console.log(Object.getOwnPropertyNames(Bar)); console.log(Object.getOwnPropertyNames(Foo)); console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(Foo)));
The result is shown below (and works as expected).
There is a CodePen if you want to try it out.
Prototype chain using function constructor
In this pattern, we use the new
keyword before a function. The function call is also called a constructor call in JavaScript when we use new keyword. We create two functions, Foo
and Bar
. We define function properties (or methods) on the prototype object of the functions. And we chain all the prototype objects appropriately as follows.
function Foo() {} Foo.prototype.speak = function() { console.log("speak"); }; function Bar() {} Bar.prototype = new Foo(); Bar.prototype.speakMore = function() { console.log("speak more"); }; const bar = new Bar(); bar.speak(); // speak bar.speakMore(); // speak more
As shown above, the newly created object (bar) has a prototype object which is nothing but the prototype object of the Bar
function. The prototype object of Bar
function also has a prototype object which is nothing but the prototype object of Foo
function. We can verify by printing the own properties of each of the object prototypes.
console.log(Object.getOwnPropertyNames(bar)); console.log(Object.getOwnPropertyNames(Bar.prototype)); console.log(Object.getOwnPropertyNames(Foo.prototype)); console.log(Object.getOwnPropertyNames(Object.prototype));
The screenshot below shows the result.
It is slightly different from the previous result (Pattern 1). Foo.prototype has an additional constructor
property. By default, a function prototype has constructor property. We redefined a new prototype object for Bar
function with Bar.prototype = new Foo()
. So, the original prototype of Bar
function is replaced with an object whose prototype object is the same as the prototype object of Foo
function. (CodePen)
Prototype chain using ES6 class
ES6 class is mostly syntactic sugar for building prototype chains. Consider the following class.
class Foo {} console.log(typeof Foo); // function
Foo is a class. But typeof Foo
returns a function. As a function, Foo has a prototype.
Let us add a method to our class. And print out the prototype properties.
class Foo { speak() { console.log('speak'); } } console.log(Object.getOwnPropertyNames(Foo.prototype)); // ['constructor', 'speak']
We get constructor
and speak
as own property names for Foo’s prototype. So, the class keyword in JavaScript helps us to define a prototype for the function named after the class.
We can extend the prototype chain using the extend keyword. Define a new class Bar as follows.
class Bar extends Foo { speakMore() { console.log('speak more'); } } console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype); // true
As we now know, Bar
class defines a prototype object for Bar
function. The extends keyword makes Foo`s prototype object the prototype for Bar`s prototype object. In short, we chain the prototypes.
Finally, our example can be rewritten using the class pattern as follows.
class Foo { speak() { console.log('speak'); } } class Bar extends Foo { speakMore() { console.log('speak more'); } } const bar = new Bar(); bar.speak(); // speak bar.speakMore(); // speak more
We can verify if the prototypes are chained as follows.
console.log(Object.getOwnPropertyNames(bar)); console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(bar))); console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(bar)))); console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(bar)))));
The result is shown in the screenshot below.
No magic here. CodePen is available.
Summary
I hope some of JavaScript magic is demystified in the article. Objects have properties. JavaScript looks up the own properties of an object while getting a property value. If it does not find in the own property of an object, it walks up the prototype chain till the property is found. An object defines behaviour using function properties which I also call methods. Function properties behave the same way as regular properties. JavaScript retrieves the first matching function name in the own property or prototype chain.
All objects have a prototype object. But prototype object can be null.
A function has a prototype object. Newing a function or calling the function constructor creates a new object whose prototype is the function’s prototype object.
Prototype chaining has three patterns:
- Chaining prototypes by defining prototype objects.
- Chaining prototypes using function constructor.
- Chaining prototypes using class keyword.
ES6 class is a syntactical sugar over functions. Whatever is defined within a class is the prototype of a function that goes by the same class name. So, newing a class is equivalent to calling a function constructor. And as explained, a new object is created with the prototype object being the function’s prototype (or the class). The extends keyword also helps in chaining two prototypes (as defined by the respective classes).