Adding a prototyped method to a function
function Person(name) { this.name = name; } Person.prototype.sayHello = function() { return `My name is ${this.name}.`; }; let person1 = new Person('Jack'); console.log(person1.sayHello()); // My name is Jack.
Adding a prototyped method to all string objects
String.prototype.upper = function() { return this.toUpperCase(); }; console.log("abc".upper()); // "ABC"
Adding a prototyped method to all array objects
Array.prototype.first3 = function() { return this.slice(0, 3); }; console.log([1,2,3,4,5].first3()); // [1,2,3]
Adding a prototyped method to a function
function ninja(){} ninja.prototype.sword = function(){ alert('sword'); } samurai = new ninja(); samurai.sword();
function Ninja() {} Ninja.prototype.swingSword = function () { return true; }; var ninjaA = Ninja(); assert(!ninjaA, "Is undefined, not an instance of Ninja."); var ninjaB = new Ninja(); assert(ninjaB.swingSword(), "Method exists and is callable.");
Properties added in the constructor (or later) override prototyped properties
function Ninja() { this.swingSword = function () { return true; }; } // Should return false, but will be overridden Ninja.prototype.swingSword = function () { return false; }; var ninja = new Ninja(); assert(ninja.swingSword(), "Calling the instance method, not the prototype method.");
Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist
function Ninja() { this.swung = true; } var ninjaA = new Ninja(); var ninjaB = new Ninja(); Ninja.prototype.swingSword = function () { return this.swung; }; assert(ninjaA.swingSword(), "Method exists, even out of order."); assert(ninjaB.swingSword(), "and on all instantiated objects.");
The chainable method must return this
function Ninja() { this.swung = true; } var ninjaA = new Ninja(); var ninjaB = new Ninja(); Ninja.prototype.swing = function () { this.swung = false; return this; }; assert(!ninjaA.swing().swung, "Verify that the swing method exists and returns an instance."); assert(!ninjaB.swing().swung, "and that it works on all Ninja instances.");
Prototype-Based Language
JavaScript is an object oriented language, but unlike all the class-based languages (C++, JAVA, C#, …), JavaScript is prototype-based language. In class-based languages, we write classes which can be organized into hierarchy and so advance code reuse. In prototype-based languages, there is no distinction between classes and objects. An object is used as a template for creating a new object. In addition, an object’s set of properties can be extended either at creation time or at run time. This way prototype-based language furthering code reuse. There are more differences between class-based and prototype-based languages but this is enough for now.
Let’s instantiate a new object. In order to do it we have to define function and then simply use the "new" keyword:
unction baseObj(name) { this.sayHi = function() { alert("Hi " + this.name); } this.name = name; } var ins = new baseObj("Dan");
The baseObj() function is called Object Constructor since it creates and defines an object. Later we can call:
ins.sayHi(); // Alerts "Hi Dan"
JavaScript’s Prototype
In JavaScript, as we mentioned before, we can add properties to an object even after its creation:
function animal(){ ... ... } var cat = new animal(); cat.color = "Green";
It is important to notice that the color property is added only to the cat instance. Other instances of animal will not contain the color property. But, there are times where we want to add a property to all the instances of an object. Each animal has a color and not only cats, therefore color property is relevant to all instances of animal. That’s where the prototype object of JavaScript comes in.
In JavaScript, each object has a property called “prototype”. An object’s prototype allows us adding properties to all instances of that object (even to the existing instances). For example:
var frog = new animal(); console.log(frog.color); // frog doesn't have the color property yet animal.prototype.color = "Green"; var dog = new animal(); console.log(dog.color); // will log "Green" console.log(frog.color); // will also log "Green"
This adds and initialize the color property to every present and future animal instances.
Similar to properties, we can add methods and reflects all the instances:
animal.prototype.run = function() { console.log("I am running!"); } dog.run(); // will log "I am running!"
This functionality allows us to do very useful things like extending the behavior of an Array and add a method that gets an element and removes it from the array:
Array.prototype.remove = function(elem) { var index = this.indexOf(elem); if (index >= 0) { this.splice(index, 1); } } var arr = [1, 2, 3, 4, 5]; arr.remove(4); // will keep the array to be [1, 2, 3, 5]
In this example I used the “this” keyword inside the method. Keep in mind that “this” refer to the object that calls the method. In this example when calling arr.remove(4), “this” refer to arr and therefore this.indexOf(elem) returns the index of elem in arr.
The Object Constructor Way
Besides the prototype approach, another way to define properties and methods is by doing it inside the object constructor:
function animal() { this.color = "Green"; this.run = function() { console.log("I am running!"); } } var mouse = new animal(); mouse.run(); // will log "I am running!"
This code results the same object structure as the prototype approach. Each instance of animal will have the color property and the run method.
The main advantage of this approach is that you can make a use of local variables defined inside the object constructor:
function animal() { var runAlready = false; this.color = "Green"; this.run = function() { if (!runAlready) {} console.log("I am running!"); } else { console.log("I am already running!"); } } }
Those local variable “runAlready” is acting like private members of C# and JAVA. No one can access this variable except the object’s methods.
This approach might seem more readable and convenient but actually is not always recommended, especially when adding many methods. If you don’t need to use local variables defined inside the object constructor, then there is no reason to use this approach and using prototype is better. That is because if you are going to create lots of animals, a new set of methods will be created and held in different instances each time the animal constructor is called. In the prototype approach, all the instances will share one set of methods and therefore less memory.
You can also use combine approaches whereby methods that uses private local constructor variables will be defined inside the constructor while other methods will be added using the prototype:
function animal() { var runAlready = false; this.run = function() { if (!runAlready) {} console.log("I am running!"); } else { console.log("I am already running!"); } } } animal.prototype.color = "Green"; animal.prototype.hide = function() { console.log("I am hiding!"); } var horse = new animal(); horse.run(); // will log "I am running!" horse.hide(); // will log "I am hiding!"
examples:
In a language implementing classical inheritance like Java, C# or C++ you start by creating a class--a blueprint for your objects--and then you can create new objects from that class or you can extend the class, defining a new class that augments the original class.
In JavaScript you first create an object (there is no concept of class), then you can augment your own object or create new objects from it. It's not difficult, but a little foreign and hard to metabolize for somebody used to the classical way.
//Define a functional object to hold persons in JavaScript var Person = function(name) { this.name = name; }; //Add dynamically to the already defined object a new getter Person.prototype.getName = function() { return this.name; }; //Create a new object of type Person var john = new Person("John"); //Try the getter alert(john.getName()); //If now I modify person, also John gets the updates Person.prototype.sayMyName = function() { alert('Hello, my name is ' + this.getName()); }; //Call the new method on john john.sayMyName();
Until now I've been extending the base object, now I create another object and then inheriting from Person.
//Create a new object of type Customer by defining its constructor. It's not //related to Person for now. var Customer = function(name) { this.name = name; }; //Now I link the objects and to do so, we link the prototype of Customer to //a new instance of Person. The prototype is the base that will be used to //construct all new instances and also, will modify dynamically all already //constructed objects because in JavaScript objects retain a pointer to the //prototype Customer.prototype = new Person(); //Now I can call the methods of Person on the Customer, let's try, first //I need to create a Customer. var myCustomer = new Customer('Dream Inc.'); myCustomer.sayMyName(); //If I add new methods to Person, they will be added to Customer, but if I //add new methods to Customer they won't be added to Person. Example: Customer.prototype.setAmountDue = function(amountDue) { this.amountDue = amountDue; }; Customer.prototype.getAmountDue = function() { return this.amountDue; }; //Let's try: myCustomer.setAmountDue(2000); alert(myCustomer.getAmountDue());
While as said I can't call setAmountDue(), getAmountDue() on a Person.
//The following statement generates an error. john.setAmountDue(1000);
Prototype example
//[1,2,3,4,5].duplicator(); // 1 Array.prototype.duplicator = function () {return this.concat(this);} // 2 Array.prototype.duplicator = function () { var args = Array.prototype.slice.call(this); var arrLength = this.length; for(var i = 0; i < arrLength; i++) { args.push(this[i]); } return args; }