View on GitHub

Notes

reference notes

Objects as a Design Pattern

grouping things into objects

insted of this:

const personOneName = 'John';
const personOneAge = 30;
const personTwoName = 'Jane';
const personTwoAge = 25;

we can do this:

const personOne = {
    name: 'John',
    age: 30
};
const personTwo = {
    name: 'Jane',
    age: 25
};

that way we can group things together and access them easily

but what if we are dealing with something more complicated like a shopping site with large inventory, manually creating objects for each item is not a good idea, so we can use a constructor function to create objects for us, this brings us to the next section…….

Object Constructors

constructor functions are used to create objects, they are like blueprints for creating objects, they are used to create multiple objects with the same properties and methods, they are also used to create objects with different properties and methods, they are also used to create objects with the same properties and methods but with different values.

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayHello = () => {
        console.log(`Hello, my name is ${this.name}`);
    };
}

call the constructor function with the new keyword to create a new object:

const personOne = new Person('John', 30);
console.log(personOne.name); // John
const personTwo = new Person('Jane', 25);
console.log(personTwo.name); // Jane

personOne.sayHello(); // Hello, my name is John
personTwo.sayHello(); // Hello, my name is Jane

to create the same constructor using arrow functions:

const Person = (name, age) => {
    return {
        name,
        age,
        sayHello: () => {
            console.log(`Hello, my name is ${name}`);
        }
    };
};

note that this will not work with the new keyword.

Prototypes

In simple language, a prototype is a shared object. so instead of reapeting the same methodes(that have nothing unique about them) for each instance of an object, we can use prototypes to share the same methodes for all instances of an object. look at this example: image in this example, objects on the instance level(meaning instances created by the constructor) have their own properties with unique values, but they share the same methodes on the prototype level, so if we change the methodes on the prototype level, all the instances will be affected. this is how it looks like without the prototype: image as you can see, the methodes are repeated for each instance, this is not a good idea, so we can use prototypes to solve this problem.

how its implemented

function apple(color, weight) {
    this.color = color;
    this.weight = weight;
}

apple.prototype.eat = function () {
    // add code here
};

apple.prototype.throw = function () {
    // add code here
};

// this will create a prototype for the apple object that looks like this:
apple.prototype = {
    eat()
    throw()
}
// which can be accessed by all instances of the apple object

Setting the prototype of an object using the Object.create() method.

function banana(color, weight) {
    this.color = color;
    this.weight = weight;
}

banana.prototype.eat = function () {
    // add code here
};

banana.prototype.throw = function () {
    // add code here
};

function apple(color, weight) {
    this.color = color;
    this.weight = weight;
}

apple.prototype = Object.create(banana.prototype);
// this will copy the prototype of the banana object to the apple object

// we can also add new methodes to the apple object
apple.prototype.peel = function () {
    // add code here
};

// this will create a prototype for the apple object that looks like this:
apple.prototype = {
    eat()
    throw()
    peel()
};

Prototype Property & Prototype Attribute

function apple(color, weight) {
    this.color = color;
    this.weight = weight;
}

let apple1 = new apple('red', 100); // apple1 initallized with the apple constructor function and as such its prototype attribute is `apple.prototype`

Why is Prototype Important and When is it Used?

Prototype Property: Prototype-based Inheritance

Prototype is important in JavaScript because JavaScript does not have classical inheritance based on Classes (as most object oriented languages do), and therefore all inheritance in JavaScript is made possible through the prototype property. JavaScript has a prototype-based inheritance mechanism.Inheritance is a programming paradigm where objects (or Classes in some languages) can inherit properties and methods from other objects (or Classes). In JavaScript, you implement inheritance with the prototype property. For example, you can create a Fruit function (an object, since all functions in JavaScript are objects) and add properties and methods on the Fruit prototype property, and all instances of the Fruit function will inherit all the Fruit’s properties and methods.

Prototype Attribute: Accessing Properties on Objects

Prototype is also important for accessing properties and methods of objects. The prototype attribute (or prototype object) of any object is the “parent” object where the inherited properties were originally defined.This is loosely analogous to the way you might inherit your surname from your father—he is your “prototype parent.” If we wanted to find out where your surname came from, we would first check to see if you created it yourself; if not, the search will move to your prototype parent to see if you inherited it from him. If it was not created by him, the search continues to his father (your father’s prototype parent).

Similarly, if you want to access a property of an object, the search for the property begins directly on the object. If the JS runtime can’t find the property there, it then looks for the property on the object’s prototype—the object it inherited its properties from. If the property is not found on the object’s prototype, the search for the property then moves to prototype of the object’s prototype (the father of the object’s father—the grandfather). And this continues until there is no more prototype (no more great-grand father; no more lineage to follow). This in essence is the prototype chain: the chain from an object’s prototype to its prototype’s prototype and onwards. And JavaScript uses this prototype chain to look for properties and methods of an object. If the property does not exist on any of the object’s prototype in its prototype chain, then the property does not exist and undefined is returned.

This prototype chain mechanism is essentially the same concept we have discussed above with the prototype-based inheritance, except we are now focusing specifically on how JavaScript accesses object properties and methods via the prototype object.

this

Before starting, let’s familiarize with a couple of terms:

this depends on the context of the invocation. there are 4 types of function invocations in JavaScript:

Function Invocation

Function invocation is basically when you call a function without any object or method. for example:

function sayHello(name) {
    console.log(`Hello ${name}`);
}

sayHello('John'); // this is a function invocation

dont confuse this with something like obj.sayHello('John'), this is a method invocation, not a function invocation.

this in function invocation

in function invocation, this is the global object, which is window in the browser.

function sum(a, b) {
  console.log(this === window); // => true
  this.myNumber = 20; // add 'myNumber' property to global object
  return a + b;
}
// sum() is invoked as a function
// this in sum() is a global object (window)
sum(15, 16);     // => 31
window.myNumber; // => 20, since we gave the global object(aka window) a property called 'myNumber' using `this`

At the time sum(15, 16) is called, JavaScript automatically sets this as the global object (window in a browser).

if we use this in the global scope, it will be the global object, which is window in the browser. same as using it inside a function scope.

console.log(this === window); // => true
this.myNumber = 20; // add 'myNumber' property to global object

strict mode function invocation

if we use strict mode, this will be undefined in function invocation.

Strict mode provides more security and helps to avoid some common errors. Strict mode is declared by adding the string “use strict” to the beginning of a script or a function. once we use strict mode, this will be undefined in function invocation.

function multiply(a, b) {
  'use strict'; // enable the strict mode
  console.log(this === undefined); // => true
  return a * b;
}
// multiply() function invocation with strict mode enabled
// this in multiply() is undefined
multiply(2, 5); // => 10

Pitfalls

A Common Mistake is thinking that this is the same in inner and outer functions. This is not the case. this is determined by the context of the invocation(aka call), not the context of the function definition.

const numbers = {
  numberA: 5,
  numberB: 10,
  sum: function() {
    console.log(this === numbers); // => true
    function calculate() {
      // this is window or undefined in strict mode
      console.log(this === numbers); // => false
      return this.numberA + this.numberB;
    }
    return calculate();
  }
};
numbers.sum(); // => NaN or throws TypeError in strict mode

calculate() is a function invocation (but not method invocation), thus here this is the global object window or undefined in strict mode. Even if the outer function numbers.sum() has the context as numbers object, it doesn’t have influence here. there are 2 ways to solve this problem using calculate.call(this) to invoke calculate, or the better alternative is to use arrow function.

const numbers = {
  numberA: 5,
  numberB: 10,
  sum: function() {
    console.log(this === numbers); // => true
    const calculate = () => {
      // this is numbers object
      console.log(this === numbers); // => true
      return this.numberA + this.numberB;
    }
    return calculate();
  }
};
numbers.sum(); // => 15

Method Invocation

A method is a function stored in a property of an object. For example:

const myObject = {
  // helloMethod is a method
  helloMethod: function() {
    return 'Hello World!';
  }
};
const message = myObject.helloMethod();
console.log(message); // => 'Hello World!'

this in method invocation

this is the object that owns the method in a method invocation

const calc = {
  num: 0,
  increment() {
    console.log(this === calc); // => true
    this.num += 1;
    return this.num;
  }
};
// method invocation. this is calc
calc.increment(); // => 1
calc.increment(); // => 2

straightforward, When invoking a method on an object, this is the object that owns the method.

it works the same when an object inherits a method from its prototype.

const myDog = {
  name: 'Fido';
};

const myDOG = Object.create({
    sayName() {
        console.log(this.name);
    }
});

myDOG.name = 'Fido';
myDOG.sayName(); // => Fido

Pitfalls

A common mistake happens when separating the method from the object. const alone = myObj.myMethode when the metode is called alone(), detatched from the object, you might think that this is the object myObject on which the method was defined. WRONG! since the methode is called without an object, this is the global object (function invocation).

function Pet(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => false
    console.log(`The ${this.type} has ${this.legs} legs`);
  }
}
const myCat = new Pet('Cat', 4);

// logs "The undefined has undefined legs"
// or throws a TypeError in strict mode
setTimeout(myCat.logInfo, 1000);

you might think here that setTimeout(myCat.logInfo, 1000) will call myCat.logInfo() but unfortunately this is not the case. when a methode is passed as a parameter its seperated from its object.

to fixe this we can use bind() to bind the methode to its object. but we can also use arrow function to fixe this problem.

function Pet(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log(`The ${this.type} has ${this.legs} legs`);
  }
}
const myCat = new Pet('Cat', 4);

// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000);

myCat.logInfo.bind(myCat) returns a new function that executes exactly like logInfo, but has this as myCat, even in a function invocation.

Constructor Invocation

its preformed when new keyword is used to create a new object. as we saw in Object constructor section.

function Country(name, traveled) {
  this.name = name ? name : 'United Kingdom';
  this.traveled = Boolean(traveled); // transform to a boolean
}
Country.prototype.travel = function() {
  this.traveled = true;
};
// Constructor invocation
const france = new Country('France', false);
// Constructor invocation
const unitedKingdom = new Country;
france.travel(); // Travel to France

this in constructor invocation

this is the newly created object in a constructor invocation. lets check the context in the following example.

function Foo () {
  // this is fooInstance
  this.property = 'Default Value';
}
// Constructor invocation
const fooInstance = new Foo();
fooInstance.property; // => 'Default Value'

new Foo() is a constructor invocation where the context of this is the newly created object fooInstance.

Pitfalls

A common mistake is to forget the new keyword when invoking a constructor. when you forget the new keyword, this is the global object (function invocation).

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
const car = Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount // => 4
car === window // => true

You might think it works well for creating and initializing new objects.However, this is window object in a function invocation (see Function invocation), thus Vehicle(‘Car’, 4) sets properties on the window object. This is a mistake. A new object is not created.

Indirect Invocation

when a function is called using call() or apply() methods its indirect invocation. they are from the list of methods that are available on every function object, and they are used to invoke a function with a specific context. call() and apply() are very similar, the only difference is that call() accepts a list of arguments, while apply() accepts an array of arguments.

function sum(number1, number2) {
  return number1 + number2;
}
sum.call(undefined, 10, 2);    // => 12
sum.apply(undefined, [10, 2]); // => 12

this in indirect invocation

this is the first argument of .call() or .apply() in an indirect invocation

const rabbit = { name: 'White Rabbit' };
function concatName(string) {
  console.log(this === rabbit); // => true
  return string + this.name;
}
// Indirect invocations
concatName.call(rabbit, 'Hello ');  // => 'Hello White Rabbit'
concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'

The indirect invocation is useful when a function should be executed with a specific context. For example, to solve the context problems with function invocation, where this is always window or undefined in strict mode (see Function invocation). It can be used to simulate a method call on an object (see the previous code sample).

Another practical example is creating hierarchies of classes in ES5 to call the parent constructor:

function Runner(name) {
  console.log(this instanceof Rabbit); // => true
  this.name = name;
}
function Rabbit(name, countLegs) {
  console.log(this instanceof Rabbit); // => true
  // Indirect invocation. Call parent constructor.
  Runner.call(this, name);
  this.countLegs = countLegs;
}
const myRabbit = new Rabbit('White Rabbit', 4);
myRabbit; // { name: 'White Rabbit', countLegs: 4 }

Resource (Arrow function & Bound function)