NOTE: These are notes from Colt Steele's The Web Developer Bootcamp (from before the 2021 update)

NOTE: There are more notes about this subject from another course here

this keyword

Intro

  • foundation for OOP
  • it is a reserved keyword in JavaScript
  • usually determined by how a function is called (what we call 'execution context')
  • can be determined using 4 rules
    • global
    • object/implicit
    • explicit
    • new

Global Context

  • the broadest rule of them all
  • this IS NOT inside of a declared object (i.e. there has not been an object defined which contains the keyword this --- the keyword this is 'in the wild')
  • in this case it refers to the global object, which in the browser is the window object
    • every object you create in the global scope is attached to the window object
      let person = 'Elie'
      window.person               // 'Elie'
      window.person === person    // true
console.log(this);              // window

function whatIsThis () {
  return this;
}

function variablesInThis () {
  // since the value of `this` is the window
  // all we are doing here is creating a global variable ==> we can use it outside the 
  // function --- this is bad practice --- variables that we want to use in multiple
  // functions should be declared at the top of the code even if they aren't assigned until later
  this.person = 'Elie';
}
  
console.log(person);            // 'Elie'

whatIsThis();                   // window

Global with Strict

  • when strict mode is enabled, which you can do by adding "use strict", the value of this when inside functions is undefined
"use strict" 

console.log(this);              // window

function whatIsThis () {
  return this;
}

function variablesInThis () {
  // since we are in strict mode this is undefined
  this.person = 'Elie';
}
  
variablesInThis();              // TypeError, can't set a person on undefined

whatIsThis();                   // undefined

Implicit/Object

  • when this IS inside of a declared object, the value of this will always be the closest parent object
// strict mode does not make a difference here

let person = { 
  firstName: 'Elie',
  sayHi: function () {
    return 'Hi ' + this.firstName;    // closest parent object is person ==> `this` is the 
  },                                  // person object
  determineContext: function () {
    return this === person;          
  }
}

person.sayHi();                       // "Hi Elie"

person.determineContext();            // true

Nested Objects

let person = { 
  firstName: 'Colt',
  sayHi: function () {
    return 'Hi ' + this.firstName;    
  },                                  
  determineContext: function () {
    return this === person;          
  },
  dog: {
    sayHello: 'Hello ' + this.firstName,    // dog is closest parent object
    determineContext: function () {
      return this === person;
    }     
  } 
}

person.sayHi();                       // "Hi Colt"

person.determineContext();            // true

person.dog.sayHello();                // "Hello undefined" --- undefined because dog doesn't
                                      // have a firstName property

person.dog.determineContext();        // false --- `this` is refering to dog not person

Explicit Binding

  • choose what we want the context of this to be using call, apply, or bind
  • these can only be used on functions
  • call and bind take infinite number of parameters, while apply only takes 2
  • use bind when we want full control over what this will refer to --- it will have precedence over call and apply
Name of Method | Parameters                 | Invoke Immediately
---------------|----------------------------|----------------------
Call           | thisArg, a, b, c, d, ...   | Yes
Apply          | thisArg, [a, b, c, d, ...] | Yes
Bind           | thisArg, a, b, c, d, ...   | No

Call

  • using call to fix the nested objects (like in the above example)
    let person = { 
      firstName: 'Colt',
      sayHi: function () {
        return 'Hi ' + this.firstName;    
      },                                  
      determineContext: function () {
        return this === person;          
      },
      dog: {
        sayHello: 'Hello ' + this.firstName,  
        determineContext: function () {
          return this === person;
        }     
      } 
    }
    
    person.sayHi();                               // "Hi Colt"
    person.determineContext();                    // true
    
    person.dog.sayHello.call(person);             // "Hello Colt" --- now `this` is the person
                                                  // object so `this.firstName` now has the value
                                                  // "Colt" instead of undefined
    
    person.dog.determineContext().call(person);   // true --- now that `this` is person the dog
                                                  // object's determineContext function becomes true
  • call is commonly used to avoid code duplication
    • BAD example with duplication:
      let colt = { 
        firstName: 'Colt',
        sayHi: function () {
          return 'Hi ' + this.firstName;
        }   
      }
      
      let elie = { 
        firstName: 'Elie',
        // look at all this duplication :(
        sayHi: function () {
          return 'Hi ' + this.firstName;
        }   
      }
      
      colt.sayHi();         // "Hi Colt"
      
      elie.sayHi();         // "Hi Elie" -- but we had to duplicate the whole function from above
      
      // How can we refactor the duplication using call?
      
      // How can we 'borrow' the sayHi function from colt and set the value of `this` to be elie?
      
    • GOOD example without duplication:
      let colt = { 
        firstName: 'Colt',
        sayHi: function () {
          return 'Hi ' + this.firstName;
        }   
      }
      
      let elie = { 
        firstName: 'Elie'
      }
      
      colt.sayHi();                 // "Hi Colt"
      
      elie.sayHi.call(colt);        // WRONG --- elie doesn't have a sayHi function and `this
                                    // would be calling "Colt" instead of "Elie"
      
      colt.sayHi.call(elie)         // "Hi Elie" --- called colt with the sayHi function and
                                    // then called `this` as elie

Apply

  • almost identical to call - except for the parameters
let colt = { 
  firstName: 'Colt',
  sayHi: function () {
    return 'Hi ' + this.firstName;
  },
  addNumbers: function (a, b, c, d) {
    return this.firstname + ' just calculated ' + (a + b + c + d);
  }
}

let elie = { 
  firstName: 'Elie'
}
  
colt.sayHi();                                // "Hi Colt"

colt.sayHi.apply(elie);                      // "Hi Elie" --- same so far because no parameters

// ...what happens when we start adding arguments?

colt.addNumbers(1, 2, 3, 4);                 // "Colt just calculated 10"

colt.addNumbers.call(elie, 1, 2, 3, 4);      // "Elie just calculated 10"

colt.addNumbers.apply(elie, [1, 2, 3, 4]);   // "Elie just calculated 10"

Bind

  • the parameters work like call, but it returns a function with the context of this bound already
let colt = { 
  firstName: 'Colt',
  sayHi: function () {
    return 'Hi ' + this.firstName;
  },
  addNumbers: function (a, b, c, d) {
    return this.firstname + ' just calculated ' + (a + b + c + d);
  }
}

let elie = { 
  firstName: 'Elie'
}

let elieCalc = colt.addNumbers.bind(elie, 1, 2, 3, 4);    // returns a function definition to us

elieCalc();                                               // "Elie just calculated 10"

// with bind, we do not need to know all the arguments up front

let elieCalc2 = colt.addNumbers.bind(elie, 1, 2);         // this is called partial application
                                                          // because we don't need all the
                                                          // parameters to the function when we bind
                                                          // it --- we only need to know what we
                                                          // want the value of `this` to be

elieCalc2(3, 4);                                          // "Elie just calculated 10"
  • bind commonly used to set the context of this for a function to be called at a later time --- this commonly happens with asynchronous code (i.e. code that doesn't run line by line)
let colt = {
  firstName: 'Colt',
  sayHi: function () {
    setTimeout(function () {
      console.log('Hi ' + this.firstName);    // while `this` is in a parent object, because 
    }, 1000)                                  // setTimeout is called at a later time `this` does
  }                                           // not refer to the parent object --- it refers to
}                                             // the global object (the window object)

colt.sayHi();                                 // "Hi undefined" (1000 milliseconds later)

// call and apply execute immediately so we need to use bind with the setTimeout function, which
// won't execute until later

let colt = {
  firstName: 'Colt',
  sayHi: function () {
    setTimeout(function () {
      console.log('Hi ' + this.firstName);    
    }.bind(this), 1000)                       // inside the colt object `this` refers to the colt
  }                                           // object, so we are binding the correct value of
}                                             // `this` to what we want when the function inside
                                              // setTimeout is called

colt.sayHi();                                 // "Hi Colt" (1000 milliseconds later)

// can get the same result by passing colt to the bind method, but more commonly you will see it
// with `this`

the new keyword

  • we can set the context of the keyword this using the new keyword (will be discussed further in notes about OOP)
  • when new is used a new object is created --- new is used with a function, and inside the function definition this refers to the new object that is created
function Person (firstName, lastName) {
  this.firstName = firstName;      
  this.lastName = lastName;
}

let elie = new Person ('Elie', 'Schoppik');   // the `new` keyword is used, therefore the references
                                              // to `this` inside the function are refering to the 
                                              // new object created, which is the elie object

elie.firstName;                               // "Elie"
elie.lastName;                                // "Schoppik"

Copyright © 2022