Every function in JavaScript has a reference type property called
There is a core prototype Object constructor in JavaScript carries a main prototype. This main prototype is then referred to by all Objects.
EXAMPLE:
let myObj = {};
let prototype = Object.getPrototypeOf(myObj);
console.log(prototype === Object.prototype);
// RESULT: true because the prototype for myObj points to the prototype for the core Object constructor.
How JavaScript Uses Protoypes
The JS engine will first check for a property on the object. For example, if the property "name" is on the Object, it will use the value associated there. If this is not on the object, it will then check the prototype to see if "name" exists. If so, it uses the associated value in the prototype.
Methods for Checking the Prototype
- .hasOwnProperty(object) will return true if property is located on the object.
- Object.getPrototypeOf(object) will return the Object's prototype.
- As shown in the first example, prototype === Object.prototype will return true if the same and falseif not.
↓ Examples ↓
const passenger = {
name: "John"
};
console.log("name" in passenger); // RESULT: true
console.log(passenger.hasOwnProperty('name')); // RESULT: true
console.log('hasOwnProperty' in passenger); // RESULT: true
console.log(passenger.hasOwnProperty('hasOwnProperty')); // RESULT: false because 'hasOwnProperty' actually comes from the main Object constructor prototype.
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')) // RESULT: true
console.log(Object.getPrototypeOf(passenger )) // RESULT: Object { …a long list detailing the prototype }
Custom Prototypes
Functions and properties and be pushed to the prototype for a custom constructor or function.
The benefits of this are twofold:
- The prototype a much more memory efficient way for adding properties, functions and methods to objects.
- Prototype chaining can be used to implement inheritance.
MyConstructor.prototype.propertyName = function() {
...code
}
↓ Example ↓
function Flight(airlines, flightNumber) {
this.airlines = airlines;
this.flightNumber = flightNumber;
}
// Here we have removed the
// this.display function
// from the custom constructor and
// pushed it to the constructor's prototype.
function Flight.prototype.display = function() {
this.display = function() {
console.log(this.airlines);
console.log(this.flightNumber);
};
}
let flight1 = new Flight("American Airlines", "AA123");
let flight2 = new Flight("Southwest", "SW497");
// Because the display function
// is passed in the prototype, the
// display function still works.
flight1.display(); // RESULT: "American Airlines" "AA123"
flight2.display(); // RESULT: "Southwest" "SW497"
Adding Multiple Items to the Prototype
While the above method can be duplicated to add multiple items to the prototype, a better method is to use aN Object Literal expression.
MyConstructor.prototype = function() {
...code;
...more code;
...more code
}
NOTE: When we assign using the literal form, given that the constructor is part of the core prototype, otherwise the JS engine will treat a function made using the Literal expression as a generic object instead of a constructor. To avoid this, add
constructor: MyConstructorName,
to the top of the constructor.
↓ Example ↓
function Flight(airlines, flightNumber) {
this.airlines = airlines;
this.flightNumber = flightNumber
}
// In this example the literal method
// for pushing properties is used to add
// the this.display function and
// also overwrite the prototype's
// built-in toString function.
Flight.prototype = {
// Add this to establish this constructor
constructor: Flight,
display: function() {
console.log(this.airlines);
console.log(this.flightNumber);
},
toString: function() {
return "[Flight " + this.airlines + "," + this.flightNumber + "]";
}
}
let flight1 = new Flight("American Airlines", "AA123");
let flight2 = new Flight("Southwest", "SW497");
// Test the display function
flight1.display(); // RESULT: "American Airlines" "AA123"
flight2.display(); // RESULT: "Southwest" "SW497"
// Test the toString function
console.log(flight1.toString()); // RESULT: "[Flight American Airlines,AA123]"
console.log(flight2.toString()); // RESULT: "[Flight Southwest,SW497]"
Explicit Creatation Using Object.Create()
Object.create(Object.prototype, {
prop1Name: {},
prop2Name: {}
}
&
darr;
Example & darr;
// Add to the built-in prototype giving all objects access to this
Object.create(Object.prototype, {
name: {
configurable: true,
enumerable: true,
value: "Roger",
writable: true
},
});
// Linking one objects prototype reference to another
let project1 = {
name: "Road Work",
display: function() {
console.log(this.name);
}
};
// Link project2 to project1 to pass all parts of the prototype(functions, properties, etc)
// Here, project2 inherits name and display, but changes name.
let project2 = Object.create(project1, {
name: {
configurable: true,
enumerable: true,
value: "Bridge Work",
writable: true
}
})
project1.display(); // RESULT: Road Work
project2.display(); // RESULT: Bridge Work
Constructor Inheritance
Allow Objects created with a function to inherit the constructor
Object.create(Object.protoype, {
prop1Name: {},
prop2Name: {}
}
This happens automatically when a new object is created. ↓
MyObject.prototype = Object.create(Object.prototype, {
constructor: {
configurable: true,
enumerable: true,
value: MyObject,
writable: true
}
});
You can pass the constructor from the parent function to the child object's prototype like this: ↓
// Sets up the Doctor constructor
function Doctor(name) {
this.name = name;
};
// Adds two functions to the Doctorp prototype
Doctor.prototype.treat = function() {
return "treated";
};
Doctor.prototype.toString = function() {
return "[Doctor " + this.name + "]";
};
// Sets up Surgeon constructor
function Surgeon(name, type) {
this.name = name;
this.type = type;
}
// Forces the Surgeon prtotype to inherit the Doctor prototype giving Sugeon access to the functions added above
Surgeon.prototype = new Doctor();
// The above leaves Surgeon with the Doctor constructor, so the below will restore the Surgeon constructor
Surgeon.prototype.constructor = Surgeon;
// Overwrite the toString that Sugeon inherited from Doctor
Surgeon.prototype.toString = function() {
return "[Surgeon " + this.name + "]";
};
// Put this all in action
const doctor = new Doctor("John");
const surgeon = new Surgeon("Bob", "Dental")
// Test both the treated() functions on each prototype
// This was inherited, so it should be the same.
console.log(doctor.treat()); // RESULT: treated
console.log(surgeon.treat()); // RESULT: treated
// Test both the toString() method on each prototype
// This was inherited, but over-written on Surgeon
console.log(doctor.toString()); // RESULT: [Doctor John]
console.log(surgeon.toString()); // RESULT: [Surgeon Bob]
// Test if Surgeon is actually and instanceof Doctor
console.log(surgeon instanceof Doctor); // RESULT: true because we forced inheritence
// For good measure, test Surgeon and the core JS Object, as well
console.log(surgeon instanceof Surgeon); // RESULT: true
console.log(surgeon instanceof Object); // RESULT: true
Though rarely needed, you can pass properties from one Constructor to another Constructor when using .call(this, paramName).
// Sets up the Doctor constructor
function Doctor(name) {
this.name = name;
};
// Sets up Surgeon constructor
function Surgeon(name, type) {
// Here is where the new Surgeon object will get Doctor this and name perameters
Doctor.call(this, name);
// Now define the rest of the Surgeon constructor
this.name = name;
this.type = type;
}
You can invoke a function on one Object's prototype from another Object using myObject1.prototype.functionName.call(this)
. These Objects do not have to be related in any way.
↓ Example ↓
Surgeon.prototype.treat = function() {
// Directly invoke the treat() function from the Doctor prototype and return the result
return Doctor.prototype.treat.call(this) + "and operated"
}
Below is a final example that brings much of this together into a little more real-world use. ↓
let dragon = {
name: 'Tanya',
fire: true,
fight() {
return 5
},
sing() {
if (this.fire) {
return `I am ${this.name}, the breather of fire`
}
}
}
console.log(dragon.name); // RESULT: Tanya
console.log(dragon.fire); // RESULT: true
console.log(dragon.fight()); // RESULT: 5
console.log(dragon.sing()); // RESULT: I am Tanya, the breather of fire
let lizard = {
name: 'Kiki',
fight() {
return 1
}
}
console.log(lizard.name); // RESULT: Tanya
console.log(dragon.fire.bind(lizard)); // RESULT: This will not work
console.log(dragon.fight.bind(lizard)()); // RESULT: 5
console.log(dragon.sing.bind(lizard)()); // RESULT: This works, but the fire property is not set
// Instead, inheriting dragon's prototype will give lizard direct access to what dragon has.
// This is just for demonstration. Do not use this in practive as it can be quite bad for performance
lizard.__proto__ = dragon;
console.log(lizard.name); // RESULT: Tanya
console.log(lizard.fire); // RESULT: true
console.log(lizard.fight()); // RESULT: 1
console.log(lizard.sing()); // RESULT: I am Kiki, the breather of fire
console.log(lizard.isPrototypeOf(dragon)); // RESULT: true let dragon = {
name: 'Tanya',
fire: true,
fight() {
return 5
},
sing() {
if (this.fire) {
return `I am ${this.name}, the breather of fire`
}
}
}
console.log(dragon.name); // RESULT: Tanya
console.log(dragon.fire); // RESULT: true
console.log(dragon.fight()); // RESULT: 5
console.log(dragon.sing()); // RESULT: I am Tanya, the breather of fire
let lizard = {
name: 'Kiki',
fight() {
return 1
}
}
console.log(lizard.name); // RESULT: Tanya
console.log(dragon.fire.bind(lizard)); // RESULT: This will not work
console.log(dragon.fight.bind(lizard)()); // RESULT: 5
console.log(dragon.sing.bind(lizard)()); // RESULT: This works, but the fire property is not set
// Instead, inheriting dragon's prototype will give lizard direct access to what dragon has.
// This is just for demonstration. Do not use this in practive as it can be quite bad for performance
lizard.__proto__ = dragon;
console.log(lizard.name); // RESULT: Tanya
console.log(lizard.fire); // RESULT: true
console.log(lizard.fight()); // RESULT: 1
console.log(lizard.sing()); // RESULT: I am Kiki, the breather of fire
console.log(lizard.isPrototypeOf(dragon)); // RESULT: true