The prototype exists in all objects as a built-in property. This property helps to share functions between object instances…
Photo by Blake Connally Unsplash
Table of Contents
Open Table of Contents
Functional Instantiation
Functional instantiation means creating objects with functions. These functions are called constructor functions.
// Constructor function
function Todo(title, description, creationDate){
let todo = {};
todo.title = title;
todo.description = description;
todo.creationDate = creationDate;
todo.archiveLocation = "";
todo.moveToArchive = function (archiveLocation) {
console.log(`${this.title} moved to archive.`);
this.archiveLocation = archiveLocation;
}
return todo; // Return created object.
}
const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
But the above example contains a problem. This problem is overusing memory
because we are creating a new moveToArchive
function every time an object
is created and we are not sharing this function among the object instances. The solution is …
Functional Instantiation with Shared Methods
const todoMethods = {
moveToArchive(archiveLocation) {
console.log(`${this.title} moved to archive.`);
this.archiveLocation = archiveLocation;
}
};
function Todo(title, description, creationDate){
let todo = {};
todo.title = title;
todo.description = description;
todo.creationDate = creationDate;
todo.archiveLocation = "";
// Share the function between instances.
todo.moveToArchive = todoMethods.moveToArchive;
return todo;
}
const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
Now we are sharing the moveToArchive
function between object instances but
when we plan to add a function to the todoMethods
we should also change the
constructor function Todo
. With the help of the Object.create
function we can
copy the properties of todoMethods
implicitly.
//...
function Todo(title, description, creationDate){
let todo = Object.create(todoMethods);
todo.title = title;
todo.description = description;
todo.creationDate = creationDate;
todo.archiveLocation = "";
return todo;
}
// ...
Now when we add a function to the todoMethods
we do not have to change the
Todo
constructor function.
Prototypal Instantiation
In Javascript, every object has a built-in property the prototype
. We
can use this property rather than managing todoMethods
in another object.
Array functions like concat
, filter
, and find
are inherited using the
prototype property. (Array.prototype.concat
, Array.prototype.filter
)
function Todo(title, description, creationDate){
// Now we are inheriting functions from prototype
let todo = Object.create(Todo.prototype);
todo.title = title;
todo.description = description;
todo.creationDate = creationDate;
todo.archiveLocation = "";
return todo;
}
// Put the function into prototype.
Todo.prototype.moveToArchive = function(archiveLocation) {
console.log(`${this.title} moved to archive.`);
this.archiveLocation = archiveLocation;
};
const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
myTodo.moveToArchive();
What About Static Methods?
Static methods are defined directly as object property due to they do not require the instance and they are not overridden.
Todo.prototype.moveToArchive = function()...
Todo.staticMethod = function()...
We can use the hasOwnProperty
function. This function returns true if the
property exists in the object itself(not in its prototype) otherwise returns false.
//...
const myTodo = Todo("myTodo-title", "myTodo-description", new Date());
console.log(myTodo.hasOwnProperty('description')); // true
console.log(myTodo.hasOwnProperty('staticMethod')); // false
console.log(myTodo.hasOwnProperty('moveToArchive')); // true ->
// Because we are creating the todo with Object.create(Todo.prototype)
What is the ‘this’ keyword and how does inheritance happen with the prototypes?
When we call a property of an object, JS checks that the object has its own property (like above). If the object has the corresponding property, evaluation finishes otherwise JS looks to the object’s prototype. (i.e. the parent object)
Let’s take a look at the example from MDN:
const parent = {
value: 2,
method() {
return this.value + 1;
},
};
// In this case `this` keyword refers to the 'parent' object.
console.log(parent.method()); // 3
const child = {
__proto__: parent, // We are setting the object's prototype.
};
// In this case `this` keyword refers to the `child` object.
// `child` object neither has property `method` and `value`
// These are resolved using the child's prototype which is
// the `parent` object.
console.log(child.method()); // 3
// Now the `child` has own property `value`
child.value = 10;
// this.value = 10
console.log(child.method()); // 11
// Also we can override the `method`
child.method = function () {
return this.value + 10;
}
console.log(child.method()); // 20
console.log(child.__proto__.method()); // 3
// Because now the `this` keyword refers to `child.__proto__`, the
// `parent` object.
console.log(child.__proto__.method.apply(child)); // 11
// with the `apply` function we are setting
// the `this` property to `child`. But the `method` is coming
// from the `parent` object.
console.log(child.method.apply(parent)); // 12
// the child.method is coming from the `child` which is overridden
// at line 28, and we are setting the `this` property to
// the `parent` object so the `value` property is equal to 2.
// so the result is 12.
Pseudoclassical Instantiation — The ‘new’ Keyword
With the new keyword we can omit the call to function Object.create
because the new keyword does this automatically.
function Todo(title, description, dateCreated) {
// Below statement automatically included
// when we call new Todo(...)
// const this = Object.create(Todo.prototype)
this.name = name;
this.energy = energy;
// Below statement automatically included
// when we call new Todo(...)
// return this ;
}
const todo = new Todo("myTodo-title", "myTodo-description", new Date());
Prototype Chains
Every object has a field prototype but think about it the prototype field itself is an object so also prototype field has the prototype property. This continues like a chain until one of the prototypes is equal to the null. The null value terminates the prototype chain.
const o = {
a: 1,
b: 2,
__proto__: {
b: 3,
c: 4,
},
};
// o = {a: 1, b: 2}
// o.[[Prototype]] = {b:3, c:4}
// o.[[Prototype]].[[Prototype]] = Object.prototype
// o.[[Prototype]].[[Prototype]].[[Prototype]] = null | End of the chain
console.log(Object.prototype.__proto__); // null
The Complete Example
function Animal(name){
this.name = name;
}
Animal.prototype.eat = function(){
console.log(`${this.name} is eating.`);
}
function Lion(name, age) {
Animal.call(this, name); // We are calling the base constructor.
this.age = age;
}
Lion.prototype.walk = function() {
console.log(`${this.name} is walking`);
}
Lion.roar = function () {
console.log("Static function is called.");
}
Object.setPrototypeOf(Lion.prototype, Animal.prototype); // Inheritance
const animal = new Animal("An Animal");
animal.eat(); // An Animal is eating.
const lion = new Lion("The Lion", 8); // The Lion is eating.
lion.eat();
lion.walk(); // The Lion is walking
// animal.walk(); Error animal.walk is not a function
// lion.roar(); Error lion.roar is not a function
Lion.roar(); // Static function is called.
How to write this example using modern Javascript (ES6)?
class Animal {
constructor(name){
this.name = name;
}
eat(){
console.log(`${this.name} is eating.`);
}
};
class Lion extends Animal {
constructor(name, age){
super(name);
this.age = age;
}
walk(){
console.log(`${this.name} is walking`);
}
static roar(){
console.log("Static function is called.");
}
};
const animal = new Animal("An Animal");
animal.eat(); // An Animal is eating.
const lion = new Lion("The Lion", 8); // The Lion is eating.
lion.eat();
lion.walk(); // The Lion is walking
// animal.walk(); Error animal.walk is not a function
// lion.roar(); Error lion.roar is not a function
Lion.roar(); // Static function is called.
Thanks for reading…