Object Oriented Programming (OOP)

  • a programming model based around the idea of objects
  • the objects are constructed from what are called classes, which are like the blueprints of the objects --- objects created from classes are called instances
  • strive to make classes abstract and modular so they can be shared through all parts of an application
  • JavaScript does not have classes built into it, but we can mimic the behaviour with things it does have --- functions and objects

Constructor Functions

  • this is what JS uses as blueprints instead of classes
  • example:
    function House (bedrooms, bathrooms, numSqft) {
      this.bedrooms = bedrooms;
      this.bathrooms = bathrooms;
      this.numSqfy = numSqft;
    }
  • by convention, the function names are capitalized
    • technically changes nothing, but it lets other developers know it's a constructor function
  • we are attaching properties onto the keyword this --- but when being defined this refers to the global object (window) --- but we intend this to refer to the object that will be created using the constructor function (see next section)

new keyword

  • WRONG example:
    function House (bedrooms, bathrooms, numSqft) {
      this.bedrooms = bedrooms;
      this.bathrooms = bathrooms;
      this.numSqfy = numSqft;
    }
    
    let firstHouse = House(2, 2, 1000);   // trying to create a House object
    
    firstHouse;                           // "undefined" --- so it obviously didn't work
    firstHouse.bedrooms;                  // "Uncaught TypeError: Cannot read property 'bedrooms'
                                          // of undefined"
    • we are not returning anything from the function so our House function returns undefined
    • we are not explicitly binding this or placing it inside a declared object --- this means the value of this is the global object
  • GOOD example:
    function House (bedrooms, bathrooms, numSqft) {
      this.bedrooms = bedrooms;
      this.bathrooms = bathrooms;
      this.numSqfy = numSqft;
    }
    
    let firstHouse = new House(2, 2, 1000);  // actually created an object this time!
    
    firstHouse.bedrooms;                     // 2
    firstHouse.bathrooms;                    // 2
    firstHouse.numSqft;                      // 1000
  • example with function inside the constructor:
    function Dog (name, age) {
      this.name = name;
      this.age = age;
      this.bark = function () {
        console.log(this.name + ' just barked!');
      }
    }
    
    let rusty = new Dog('Rusty', 3);
    let fido  = new Dog('Fido', 1);
    
    rusty.bark();           // "Rusty just barked!"
    fido.bark();            // "Fido just barked!"

What is new Doing?

  • first it creates an empty object
  • then it sets this to be that empty object
  • it adds the line return this to the end of the function which follows it
    • ==> new must be used with a function or we will get a TypeError
  • finally, it adds a property onto the empty object called 'proto__' (also called 'dunder proto' because of the double underscores), which links the prototype property on the constructor function to the empty object (for more on this see the Prototypes section)

Multiple Constructors

function Car (make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
  // we can also set properties on the keyword `this` that are preset values
  this.numWheels = 4;
}

function Motorcycle (make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.numWheels = 2;
}
  • this has a lot of duplication --- so we want to refactor our code quite a bit using call and apply
// Car stays the same
function Car (make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.numWheels = 4;
}

// Motorcycle refactored using call
function Motorcycle (make, model, year) {
  Car.call(this, make, model, year);       // the `this` argument is telling the function to make
  this.numWheels = 2;                      // the Motorcycle the object refered to instead of Car
}

// Motorcycle refactored using apply
function Motorcycle (make, model, year) {
  Car.apply(this, [make, model, year]);
  this.numWheels = 2;
}

// Motorcycle refactored again using both apply and the arguments keyword --- that makes this
// version the nicest
function Motorcycle (make, model, year) {
  Car.apply(this, arguments);
  this.numWheels = 2;
}

Prototypes

Prototype Constructor Relationship Diagram

  • a circle is a function and a square is an object
  • every constructor function has a property on it called 'prototype', which is an object
  • the prototype object has a property on it called 'constructor', which points back to the constructor function
  • anytime an object is created using the new keyword, a property called 'proto__' gets created, linking the object and the prototype property of the constructor function
    • the prototype is shared among all objects created by that constructor function
// constructor function
function Person (name) {
  this.name = name;
}

// it hs a prototype property
Person.prototype;                           // ▼ {constructor: f}
                                            //   ▶ constructor: f Person(name)
                                            //   ▶︎ __proto__: Object ︎

// objects created from the Person constructor
let elie = new Person('Elie');
let colt = new Person('Colt');

// since we used `new`, we have established a link between the object and the prototype property
// that we can access using __proto__
elie.__proto__ === Person.prototype;        // true
colt.__proto__ === Person.prototype;        // true

// the Person.prototype object also has a property called constructor which points back to the
// function
Person.prototype.constructor === Person;    // true
// constructor function
function Person (name) {
  this.name = name;
}

// objects created from the Person constructor
let elie = new Person('Elie');
let colt = new Person('Colt');

Person.prototype.isInstructor = true;     // this is adding the isInstructor as a
                                          // property on the prototype (note: it will always be
                                          // true for any Person object)

elie.isInstructor;                        // true
colt.isInstructor;                        // true
  • we were able to access isInstructor using the proto__ link --- linked objects can access any properties on the prototype
  • example:
    • you can create new arrays with let arr = []; --- this is shorthand for new Array --- an array only has one property, length, but arrays have many accessible methods available through proto__ * you can check this in the console with arr.__proto__ === Array.prototype // true

Prototype Chain

Prototype Chain Diagram

  • JS finds methods and properties by looking at the object --- if it can't find what you're looking for, it will check the object's proto__ --- if still not found it will check the Object prototype though it's proto__ --- if still not found it will be undefined
    • this is the Prototype Chain
  • every object has access to the Object prototype
  • the Object prototype is linked to null --- this is where the prototype chain ends
  • in JS every object has a method called hasOwnPropery which returns true if the object has a property specified as a parameter of the hasOwnProperty method
    • example: arr.hasOwnProperty('length'); // true
    • the hasOwnProperty method can be found in the Object proto__ within the Array proto__
function Person (name) {
  this.name = name;
  this.sayHi = function () {
    return 'Hi ' + this.name;
  }
}

elie = new Person('Elie');
elie.sayHi();           // "Hi Elie"

// this code works but it inefficient

// every time we make an object using the new keyword we have to redefine the `sayHi` function
// , but it is the same for everyone

// here is a refactored version with `sayHi` on the prototype instead
function Person (name) {
  this.name = name;
}
Person.protoype.sayHi = function () {
  return "Hi " + this.name;
}

elie = new Person('Elie');
elie.sayHi();           // "Hi Elie"
// place all properties do not want shared inside the constructor function
function Vehicle (make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.isRunning = false;
}

// all the shared functions are in the Vehicle.prototype
Vehicle.prototype.turnOn = function () {
  this.isRunning = true;
}

Vehicle.prototype.turnOff = function () {
  this.isRunning = false;
}

Vehicle.prototype.honk = function () {
  if (this.isRunning) {
    return 'beep!';
  }
}

Copyright © 2022