Organizing programs

this in JavaScript

Learn how the value of this changes in JavaScript depending on where you use it, from undefined in strict mode to the object it is bound to inside a method.

this is a keyword whose value depends on how a function is called, not where you wrote it (except for arrow functions).

Getting this wrong breaks object methods and event handlers. Five minutes here saves hours later.

this in strict mode

In strict mode, this at the top level of a script or function is undefined.

Without strict mode, the same position often gives you the global object. In a browser that is usually window.

this in methods

A method is a function stored on an object.

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive() {
    console.log(`Driving a ${this.maker} ${this.model} car!`)
  }
}

car.drive()
//Driving a Ford Fiesta car!

Here drive() is shorthand for drive: function() { ... }. When you call car.drive(), this is car.

You can attach a method later:

const car = {
  maker: 'Ford',
  model: 'Fiesta'
}

car.drive = function() {
  console.log(`Driving a ${this.maker} ${this.model} car!`)
}

car.drive()
//Driving a Ford Fiesta car!

Arrow functions do not bind this from the call:

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive: () => {
    console.log(`Driving a ${this.maker} ${this.model} car!`)
  }
}

car.drive()
//Driving a undefined undefined car!

Binding arrow functions

You cannot fix arrow this with bind, call, or apply. Arrow functions inherit this lexically from the surrounding scope.

Explicitly pass an object to be used as this

bind() fixes this when you create the function:

const car = {
  maker: 'Ford',
  model: 'Fiesta'
}

const drive = function() {
  console.log(`Driving a ${this.maker} ${this.model} car!`)
}.bind(car)

drive()
//Driving a Ford Fiesta car!

You can rebind an existing method:

const car = {
  maker: 'Ford',
  model: 'Fiesta',

  drive() {
    console.log(`Driving a ${this.maker} ${this.model} car!`)
  }
}

const anotherCar = {
  maker: 'Audi',
  model: 'A4'
}

car.drive.bind(anotherCar)()
//Driving a Audi A4 car!

call() and apply() set this at invocation time:

const car = {
  maker: 'Ford',
  model: 'Fiesta'
}

const drive = function(kmh) {
  console.log(`Driving a ${this.maker} ${this.model} car at ${kmh} km/h!`)
}

drive.call(car, 100)
//Driving a Ford Fiesta car at 100 km/h!

drive.apply(car, [100])
//Driving a Ford Fiesta car at 100 km/h!

apply() takes arguments as an array. call() takes them one by one.

The special case of browser event handlers

In a classic DOM listener, this is the element that received the event:

document.querySelector('#button').addEventListener('click', function(e) {
  console.log(this) //HTMLElement
}

You can bind outer this instead:

document.querySelector('#button').addEventListener(
  'click',
  function(e) {
    console.log(this) //Window if global, or your context
  }.bind(this)
)

Compare car.drive() and the arrow version in your console. The output difference is the fastest way to remember when not to use arrows on methods.

If this still looks wrong, log it as the first line inside the function. That one log tells you whether the problem is binding or the data on the object.

Lesson completed