Loading...

Prototypes in JavaScript

Every function in JavaScript has a reference type property called prototype All Objects created by this function carry that same prototype, though, technically, it is not a copy of the original prototype, but, instead is only have a reference the original.

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() allows you to directly add to any prototype, custom prototypes or even the core built-in prototype that all objects refer to.

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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

q
↑ Back to Top