Prototypes in Javascript

Prototypal Inheritance and the Chain Gang

First class functions?

  • Create new functions from preexisting functions at run-time
  • Store functions in collections
  • Use functions as arguments to other functions
  • Use functions as return values of other functions

A language supports first class functions if:

What is an Object

//this is an object
const myObject = {};

//you can add properties(values) and methods(functions) to an object

myObject.name = 'Sam Object';

myObject.whatIsMyName = function() {
    return this.name;
}

//That's it.

What's wrong with this Object?

I can't reuse it or make multiple copies of it.

(without call, apply, bind)

KNOW THESE WORDS

  • CLASS/FUNCTION
  • OBJECT
  • INSTANCE

...AND WHY THEY ARE DIFFERENT

ALSO IMPORTANT

  • INHERITANCE
  • CONSTRUCTOR
  • CONTEXT

http://stackoverflow.com/questions/17525450/object-vs-class-vs-function

What Are Prototypes

  • Prototypes are JUST Functions
    • new keyword is what depicts the difference between JUST a function and a constructor
  • Prototypes are SINGULAR Functions
    • Prototype functions can have a different CONTEXT but is ever only one copy of that function. This has a small memory footprint.
  • EVERY object has a prototype
  • Every object inherits from Object..and it's prototype
    • This is called prototypal inheritance​
  • Prototypes in JavaScript are mostly used to emulate classic OOP for creating complex objects and a means of inheritance.

Things to Know about Prototypes

  • Adding a new prototype to a class will affect ALL instances of the class even if they are already created.
  • Adding a new function to a class will affect NEW instances of the modified class.
  • Function of the same name and declared on a class will OVERRIDE the prototype function of the same name. This happens because of order. Prototype happens first. Constructor second.
  • Prototype is a property of the constructor.
  • A function's prototype is initially a null object. Only has purpose when function is invoked as constructor with new keyword.
function Song() {} //just a function

Song.prototype.play = function() {
    return 'playing';
}

const song = Song(); //not invoked as a constructor...no prototype
console.log(song); // undefined

const realSong = new Song(); //invoked as constructor, returns reference to new Object(function)
console.log(realSong); // Object with play method available
console.log(realSong.prototype); //has no prototype, it was CREATED using Song's prototype

Using (or not) new with function invocation

function Song() {} //just a function, nothing done in constructor(call, this.*)

Song.prototype.play = function() {
    return 'playing';
}

const song = new Song(); //invoked as constructor, returns reference to new Object(function)
console.log(song); // Object with play method available

const song2 = new Song();
console.log(song2); // Object with play method available

Song.prototype.stop = function() {
  return 'stopped';
}

console.log(song, song2); //both objects now have a stop method available

Adding a function to a prototype after instance creation

Applies to all instances already created


//Constructor logic to set object properties, NOT prototype properties
function Song(){
    this.play = function() {
        return 'Hi-jacked';
    }
} 

Song.prototype.play = function() {
    return 'playing';
}

const song = new Song(); //invoked as constructor
console.log(song.play()); // 'Hi-jacked' 

//Instance method takes precedence since constructor gets called AFTER prototype

Constructors AND Prototypes

Constructor properties take precedence in order of execution.

Create instance of object:

  1. Bind prototype properties (not copy)
  2. Invoke constructor(copy)

//Constructor logic to set object properties, NOT prototype properties
function Song() {
    this.play = function() {
        throw new Error('Not implemented. Need to inherit into genre.');
    }
} 

Song.prototype.play = function() {
    return 'playing';
}

function MetalSong() {
    this.volume = 11;
}

MetalSong.prototype = Object.create(Song.prototype);

const reignInBlood = new MetalSong();

reignInBlood.play(); // 'playing'



Constructors AND Prototypes

Why would we do that??

The prototype Inheritance Chain

  • All objects that inherit from another object receive all prototype properties of all objects up the prototype chain
  • Adding to a prototype anywhere in the prototype chain will add property all the way up the chain.
  • hasOwnProperty() will tell you if the method is defined on the current object or inherited.
  • Inheritance ONLY inherits prototypes not constructor methods.
  • Example: forEach example to support legacy browsers
if (!Array.prototype.forEach) {
 Array.prototype.forEach = function(callback, context) {
     for (var i = 0; i < this.length; i++) {
         callback.call(context || null, this[i], i, this);
     }
 };
}

Prototypal Inheritance

Prototypes have a "chain" all the way up to Object

When a function is called on an instance of an object:

  1. Check for method on instance (including class def.)
  2. Check for method on current instance prototype
  3. Check for method on prototype of object inherited from.
  4. Continue up the chain until there is no further prototype

This is why ALL functions have toString(), call(), apply(), bind() methods.


//Constructor logic to set object properties, NOT prototype properties
function Song() {
    this.play = function() {
        throw new Error('Not implemented. Need to inherit into genre.');
    }
} 

Song.prototype.play = function() {
    return 'playing';
}

function MetalSong() {
    this.volume = 11;
}

MetalSong.prototype = Object.create(Song.prototype); //prototype is an object

function DeathMetalSong() {
    this.volume = Infinity.max;
}

DeathMetalSong.prototype = Object.create(MetalSong.prototype);

const burningRottenLambs = new DeathMetalSong();

burningRottenLambs.play(); // 'playing'



Prototypal Inheritance

Prototypes have a "chain" all the way up to Object

//Constructor logic to set object properties, NOT prototype properties
function Song() {
    this.play = function() {
        throw new Error('Not implemented. Need to inherit into genre.');
    }
} 

Song.prototype.play = function() {
    return 'playing';
}

function MetalSong() {}

MetalSong.prototype = Object.create(Song.prototype); //prototype is an object

function DeathMetalSong() {}

DeathMetalSong.prototype = Object.create(MetalSong.prototype);

function climbTheRopes(obj){
  console.log(`play() is mine: ${obj.hasOwnProperty('play')}`);
  console.log(`Prototype is: ${Object.getPrototypeOf(obj)}`);
  // this is the only time you would EVER need/use proto
  // console.log(obj.__proto__);
  
  if (!Object.getPrototypeOf(obj)) {
    return;
  }
              
  climbTheRopes(Object.getPrototypeOf(obj));
}

const deadSilence = new DeathMetalSong();
climbTheRopes(deadSilence);

Things to know

  • NEVER extend the Object prototype.
  • Prototype definitions are not hoisted because of assignment
  • __proto__ : now forget about it.
  • Since a constructor is just a function you can call it like a method without new.
    • Name your classes upper case as a visual signal.
    • This will make the context of the object the global scope.
    • Example of human proofing:
function User(first, last) {
 if (!(this instanceof arguments.callee)) {
     return new User(first,last);
 }
 this.name = first + " " + last;
}

Prototypal Inheritance and The Chain Gang

By DevLeague Coding Bootcamp

Prototypal Inheritance and The Chain Gang

  • 1,509