singularity

this keyword

Function context

const student = {
  id: 123,
  name: "John Doe",
  email: "[email protected]",
  printInfo: function () {
    console.log(`${this.id} - ${this.name} - ${this.email}`);
  }
};

student.printInfo();
// 123 - John Doe - [email protected]
function orderFood() {
  console.log("Order confirmed against the name: " + this.fullName);
}

orderFood();
// non-strict mode: 
//    Order confirmed against the name: undefined

// strict-mode: 
//    Uncaught TypeError: this is undefined

Global context

In Node.js, the JavaScript code is technically not executed in a global scope. Instead, it is executed in a module scope, where commonly used modules are CommonJS and ECMAScript modules

Constructor function context

function Recipe(name, ingredients) {
  this.name = name;
  this.ingredients = ingredients;
}

Class context


class Shape {
  constructor(color) {
    this.color = color;
  }

  printColor() {
    console.log(this.color);
  }
}

const circle = new Shape("Red");
const printColorFn = circle.printColor;
printColorFn();
// Error: this is undefined

DOM event handler context

prototypal inheritance and this


function Counter(startingValue) {
  this.value = startingValue;
}

Counter.prototype.incrementFactory = function (incrementStep) {
  return function () {
    this.value += incrementStep;
    console.log(this.value);
  };
};

const counter = new Counter(0);
const increment5 = counter.incrementFactory(5);
increment5(); // NaN
increment5(); // NaN
increment5(); // NaN

solution 1: local this


...

Counter.prototype.incrementFactory = function (incrementStep) {
  const thisVal = this; // save `this` value
  return function () {
    thisVal.value += incrementStep;
    console.log(thisVal.value);
  };
};


...

increment5(); // 5
increment5(); // 10
increment5(); // 15

solution 2: arrow functions to the rescue


...

Counter.prototype.incrementFactory = function (incrementStep) {
  return () => {
    this.value += incrementStep;
    console.log(this.value);
  };
};

...

increment5(); // 5
increment5(); // 10
increment5(); // 15

const btn = document.querySelector("button");

class FormHandler {
  constructor(submitBtn) {
    submitBtn.addEventListener("click", this.submitForm);
  }

  submitForm() {
    this.sendRequest();
    // ERROR: this.sendRequest is not a function
  }

  sendRequest() {
    console.log('sending request...');
  }
}

new FormHandler(btn);

...
submitBtn.addEventListener("click", () => this.submitForm());
...

solution 3: bind


function Counter(startingValue) {
  this.value = startingValue;
}

Counter.prototype.incrementFactory = function (incrementStep) {
  // return function () {
  const incrementFn = function () {
    this.value += incrementStep;
    console.log(this.value);
  };

  // return a function with `this` bound
  // to the object used to invoke the
  // `incrementFactory` method
  return incrementFn.bind(this);
};

const counter = new Counter(0);
const increment5 = counter.incrementFactory(5);
increment5(); // 5
increment5(); // 10
increment5(); // 15

Borrowing methods with call


const john = {
  name: "John",
  sayHello() {
    console.log("Hello, I am " + this.name);
  }
};

const sarah = {
  name: "Sarah"
};

// borrow a method from john
const sayHello = john.sayHello;
sayHello.call(sarah);
// Hello, I am Sarah

Chain constructor calls

function Employee(name, age, id) {
  this.name = name;
  this.age = age;
  this.id = id;
}

function BankEmployee(name, age, id, bankName) {
  // delegate the responsibility of adding
  // "name", "age", and "id" properties to
  // the Person constructor
  Employee.call(this, name, age, id);
  this.bankName = bankName;
}

Revisit this problem

const btn = document.querySelector("button");

class FormHandler {
  constructor(submitBtn) {
    submitBtn.addEventListener("click", this.submitForm);
  }

  submitForm() {
    this.sendRequest();
    // ERROR: this.sendRequest is not a function
  }

  sendRequest() {
    console.log('sending request...');
  }
}

new FormHandler(btn);

submitBtn.addEventListener("click", this.submitForm.bind(this)); 

Summary of this