The this keyword can be very confusing. This tutorial will help you understand how it works. You will learn about how this works in different contexts and environments. These contexts include global object, functions, object and class methods and events. You will also learn about globalThis, new feature added in ES2020.

Introduction

The this is a special keyword that exists not just in JavaScript, but also in other programming languages. What is different in the case of JavaScript is that this behaves differently in different modes. In JavaScript, there are two modes: strict and non-strict. The non-strict mode is the normal mode. It is sometimes also called a “sloppy” mode.

What this refers to always depends on the execution context in which it is defined. Execution context is the current environment, or scope, in which the line of code that is being executed is declared. During runtime, JavaScript maintains a stack of all execution contexts.

The execution context at the top of this stack is the one being executed. When the execution context changes, the value of this changes as well. Let’s take a look at what this refers to in different contexts.

Note on a strict mode

The strict mode aims to help you make your JavaScript code cleaner. It does so by establishes some special rules. For example, all variables have to be explicitly declared before you can assign them value. Your functions must be declared in a global scope. Inside a function, it is forbidden to use the same name for variable as well as for function parameter.

It is also forbidden to delete immutable properties and unqualified identifiers. All these, and many other, things will throw an error. This is another difference between strict and non-strict mode. In non-strict mode many errors are silent. In strict, they are not. Everything that is against rules of strict mode will throw an error.

Aside to these rules, strict mode also changes how this behaves. For better clarity, we will discuss how in each specific context. Last thing about strict mode. If you want to switch to strict mode add 'use strict'; statement at the top of your code.

Note: You can switch on strict mode for all your code or only for a specific function. What makes the difference is where you use the _'use strict';_ statement. Use it at the top in a global scope and it will apply to all code that follows. Use it at the top of a function and it will apply only to code that follows inside that function.

“this” in a global context

In JavaScript, when this is defined in a global context, this refers by default to a Global object. In case of browser, this global object is the window object. This global object is a top-level scope. Strict mode doesn’t make a difference for this in the case of global context. Whether you are in a strict or non-strict mode, this will behave in the same way.

// global context and this in non-strict mode
console.log(this === window)
// true

// global context and this in strict mode
'use strict'
console.log(this === window)
// true

“this”, global context and Node.js

In case of Node.js, there is no window object. In Node.js, the global object is a special object called global. This means that, in a global scope, this will refer to this global. Well, almost. This is true only inside Node.js itself. To test this, first start your favorite console and type node.

This command will switch on Node.js environment so you can work with it directly. After this, you can test what this refers to in a global context. If you don’t have Node.js on your computer you can get from Node.js website.

// In node environment
> console.log(this === global)
// true

> console.log(this)
// Object [global] {
//   global: [Circular],
//   clearInterval: [Function: clearInterval],
//   clearTimeout: [Function: clearTimeout],
//   setInterval: [Function: setInterval],
//   setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
//   queueMicrotask: [Function: queueMicrotask],
//   clearImmediate: [Function: clearImmediate],
//   setImmediate: [Function: setImmediate] {
//     [Symbol(util.promisify.custom)]: [Function]
//   }
// }

If you run your code from a JavaScript file the result will be different. When you work with JavaScript files in Node.js local code is restricted to that file. Everything there is not global, but local. As a result, this doesn’t refer to global, but to module.exports.

// In node environment, in JavaScript file
console.log(this === global)
// false

console.log(this === module.exports)
// true

Functions and “this”

In case of JavaScript top-level functions, mode matters. By top-level, I mean functions declared in a global scope, not inside objects or classes. If you work in a non-strict mode, this will refer to global object, window in case of a browser.

// Function in a non-strict mode
function testThis() {
  console.log(this === window)
}
testThis()
// true

Let’s add use strict statement at the top of the function to switch on strict mode. Now, the result will be different. this will no longer refer to global object, such as window. When you try to get the value of this JavaScript will return undefined. This is because the value of this is not set now.

// Function in a non-strict mode
function testThis() {
  'use strict' // switch on strict mode for this function
  console.log(this === window)
  console.log(this)
}
testThis()
// false
// undefined

Functions, this and call() and apply()

There is a way to set the value of this when you invoke a function so it is not undefined. To do this you can use call(), apply() or bind() methods. This is called “explicit function binding”. When you use one of these methods you pass the value of this as an argument. The first two, call() and apply() are almost the same.

The difference is that apply() accepts list of arguments wile the call() accepts arguments array. apply() also allows you to use an array literal.

// Set value of this with apply()
function testThisWithApply() {
  'use strict'

  console.log('Value of this: ', this)
}
// set value of "this" to one
testThis.apply('one')
// 'Value of this: one'
// Set value of this with call()
function testThisWithCall() {
  'use strict'
  console.log('Value of this: ', this)
}
// set value of "this" to one
testThis.call('one')
// 'Value of this: one'

Functions, this and bind()

The bind() method is different. You don’t use this method when you want to invoke, or call, a function. Instead, you use the bind() method to create a new “bound” function. After that, you invoke the new “bound” function, not the original. Now, the value of this will be what you wanted it to be.

// Set value of this with bind()
function testThisWithBind() {
  'use strict'

  console.log('Value of this: ', this)
}
// Create bound function and set value of "this" to "one"
const newTestThisWithBind = testThisWithBind.bind('one')
// Invoke new "bound" function "newTestThisWithBind"
newTestThisWithBind()
// 'Value of this:  one'
// Or, with reassigning the original function
function testThisWithBind() {
  'use strict'
  console.log('Value of this: ', this)
}
// Create bound function and set value of "this" to "reassigned!"
testThisWithBind = testThisWithBind.bind('reassigned!')
// Test: Invoke now "bound" function "testThisWithBind"
testThisWithBind()
// 'Value of this:  reassigned!'

There is one important thing about bind() method to remember. It works only once. You can’t use bind() multiple times to change the value of this of “bound” function. However, you can use it multiple times with the original function to create new “bound” functions.

// Doesn't work: Try to re-set this of bound function
function testThisWithBind() {
  'use strict'

  console.log('Value of this: ', this)
}
// Create bound function
// and set value of "this" to "one"
const newTestThisWithBind = testThisWithBind.bind('one')
// Test: Invoke new "bound" function "newTestThisWithBind"
newTestThisWithBind()
// The value of "this" is not correct
// 'Value of this:  one'
// Create another bound function
// using the bound function
// and try to change value of "this"
const newerTestThisWithBind = newTestThisWithBind.bind('two')
// Test: Invoke newer "bound" function "newerTestThisWithBind"
newerTestThisWithBind()
// The value of "this" is correct
// 'Value of this: one'
// Works: Create another bound function from the original
const brandNewThisWithBind = testThisWithBind.bind('two')
// Test: Invoke newer "bound" function "brandNewThisWithBind"
brandNewThisWithBind()
// The value of "this" is correct
// 'Value of this: two'
// Test: Invoke newer "bound" function "newerTestThisWithBind"
// The value of "this" is the same
newerTestThisWithBind()
// 'Value of this: one'

Note: This is for those of you familiar with React and class components. You will probably recognize something like _this.myFunc = this.myFunc.bind(this)_ in _constructor_. What this does is that it takes a function and creates a bound function and returns it, and basically overwrites the original.

In this case, the value of _this_ here is _this_, that is the class component itself. Another option to change the binding of _this_ in this case would be using arrow function.

Arrow functions and “this”

Arrow functions, introduced in ES6, work differently than normal functions. Arrow functions don’t have their own this. They always use the same value for this as their parent, the execution context in which they are declared. Another important thing about arrow functions is that you can’t set their values of this explicitly.

When you try to use call()apply() or bind() with arrow functions nothing will happen. Arrow functions will ignore these methods.

// Arrow function inside an object
const user = {
  username: 'franky',
  email: 'franky@frankyrocks.io',
  // Get data with arrow function
  getUserWithArrowFunction: () => {
    // This refers to global object, window
    // So, this.username is like window.username
    return `${this.username}, ${this.email}.`
  },
  // Get data with normal function
  getUserWithNormalFunction: function() {
    // This refers to myObj
    // So, this.username is like myObj.username
    return `${this.username}, ${this.email}.`
  }
}
// Test the arrow function
user.getUserWithArrowFunction()
// TypeError: Cannot read property 'title' of undefined
// Test the normal function
user.getUserWithNormalFunction()
// 'franky, franky@frankyrocks.io.'
///
// Arrow functions and binding
let arrowFunctionWithBind = () => {
  'use strict'
  console.log('Value of this: ', this)
}
// Try to create bound function
// and set value of "this" to "arrow!"
arrowFunctionWithBind = arrowFunctionWithBind.bind('arrow!')
// Test: Invoke new "bound" function "arrowFunctionWithBind"
arrowFunctionWithBind()
// 'Value of this: undefined

Because of how this works in arrow functions, arrow functions are good choice for callbacks. Remember, arrow functions always inherit this from their enclosing execution context. With arrow functions, can access this within a callback without having to worry about what this is.

// Functions as callbacks
// Using normal function as a callback
const counter = {
  count: 0,
  addCount() {
    // Use normal function as a callback in setInterval
    setInterval(function() {
      // 'this' here is Global object
      // So, ++this.count is like ++window.count
      console.log(++this.count)
    }, 1000)
  }
}
// Invoke addCount() method
counter.addCount()
// NaN
// NaN
// NaN
// NaN
// NaN
// ...
// Using arrow function as a callback
const counter = {
  count: 0,
  addCount() {
    // Use arrow function as a callback in setInterval
    setInterval(() => {
      // 'this' here is the "counter" object
      // So, ++this.count is like ++counter.count
      console.log(++this.count)
    }, 1000)
  }
}
// Invoke addCount() method
counter.addCount()
// 1
// 2
// 3
// 4
// 5
// ...
///
// What "this" is
// Using normal function as a callback
const counter = {
  logThis() {
    // Use normal function as a callback in setInterval
    setInterval(function() {
      console.log(this)
    }, 1000)
  }
}
// Invoke logThis() method
counter.logThis()
// Window
// Window
// Window
// ...
// What "this" is
// Using arrow function as a callback
const counter = {
  logThis() {
    // Use normal function as a callback in setInterval
    setInterval(() => {
      console.log(this)
    }, 1000)
  }
}
// Invoke logThis() method
counter.logThis()
// { logThis: [Function: logThis] }
// { logThis: [Function: logThis] }
// { logThis: [Function: logThis] }
// ...

Objects methods and “this”

Let’s say you use this inside a function that is inside an object. In this case, the value of this will be the object in which the method is declared. This is independent of JavaScript mode.

// Create object
const animal = {
  name: 'Cat',
  class: 'Mammalia',
  order: 'Carnivora',
  genus: 'Felis',
  logAnimal: function() {
    return this;
  }
}
// Call logAnimal() method
animal.logAnimal()
// {
//   name: 'Cat',
//   class: 'Mammalia',
//   order: 'Carnivora',
//   genus: 'Felis',
//   logAnimal: [Function: logAnimal]
// }

It doesn’t matter if you declare the function inside the object or outside it and attach it.

// Create empty object
const thing = {}

// Add property to "thing" object
thing.itemName = 'Box'
// Add method to "thing" object
thing.getItemName = function() {
  return this.itemName
}
thing.returnThis = function() {
  return this
}
// Invoke getItemName() method
thing.getItemName()
// 'Box'
thing.returnThis()
// {
//   itemName: 'Box',
//   getItemName: [Function],
//   returnThis: [Function]
// }

Function constructors and “this”

When you use this in function constructors its value will always refer to the new object created with that constructor.

// Create function constructor
function Phone(model, brand) {
  this.model = model
  this.brand = brand
  this.getModelAndBrand = function() {
    // "this" refers to new Phone object
    // created using "new" keyword
    return `Model: ${this.model}, brand: ${this.brand}`
  }
  this.returnThis = function() {
    return this
  }
}
// Create new Phone object using "new" keyword
const iPhoneX = new Phone('iPhone X', 'Apple')
// Here, "this" refers to "iPhoneX"
iPhoneX.getModelAndBrand()
// 'Model: iPhone X, brand: Apple'
iPhoneX.returnThis()
// Phone {
//   model: 'iPhone X',
//   brand: 'Apple',
//   getModelAndBrand: [Function],
//   returnThis: [Function]
// }

Class methods and “this”

When you use this in class methods it will refer to the instance created with that class.

// Create new class with two properties
// add two methods
class Brain {
  constructor(numOfHemispheres, iq) {
    this.numOfHemispheres = numOfHemispheres
    this.iq = iq
  }
  getIQ() {
    // This refers to instance of Brain class
    return this.iq
  }
  learn() {
    // This refers to instance of Brain class
    this.iq += 1
  }
  watchTv() {
    // This refers to instance of Brain class
    this.iq -= 1
  }
  returnThis() {
    return this
  }
}
// Create instance of Brain class
// with 2 hemispheres and IQ of 180
const smartBrain = new Brain(2, 180)
// Log the IQ of smartBrain
smartBrain.getIQ()
// 180
// Learn something
smartBrain.learn()
// Log the IQ of smartBrain again
smartBrain.getIQ()
// 181
smartBrain.watchTv()
// Log the IQ of smartBrain again
smartBrain.getIQ()
// 180
smartBrain.returnThis()
// Brain { numOfHemispheres: 2, iq: 180 }

Events and “this”

When you use this inside event handlers, it will refer to the element to which you attached the event listener.

Create a simple button element.

<!-- Create button -->
<button class="btn">Click</button>

Attach eventListener to the button element.

// Create event handler function
handleButtonClick function() {
  console.log(this)
}
// Find the button in the DOM,
// attach event listener to it
// and pass the handler function as an argument
document.querySelector('.btn').addEventListener('click', handleButtonClick)

When you now click on the button, you will see [object HTMLButtonElement] and a lot of data. This is the button element along with all its properties and methods.

Events, “this” and arrow functions

You will get a different result if you use arrow function as a callback for the event handler. This time, you will not get the [object HTMLButtonElement], and its properties and methods. Instead, you will get [object Window], the global window object. So, use normal function if you want to use this to access the element on which the event was triggered.

If you still want to use arrow function, for whatever reason, there is a way. Add the event as a parameter to your arrow function. Then, inside that arrow function, use event.targetevent.currentTarget, to access the element. In case of the button, you will get the [object HTMLButtonElement].

// Create handler function, now arrow function
// and specify parameter for event
const handleButtonClick = (event) => {
  // Access the value passed as event, not "this"
  console.log(event)
}
// Find the button in the DOM,
// attach event listener to it
// and pass the handler function as an argument
document.querySelector('.btn').addEventListener('click', handleButtonClick)

globalThis

The globalThis is one of the features added in ES2020. This feature aims to make work with global this. That is, with the windowselfthis or frame objects in the browser and global or this in Node.js. If you work with cross-platform JavaScript, you will no longer have to worry about using the right object.

Instead, you can use the newly added globalThis. With globalThis, you will always automatically select the correct global object no matter the platform. That said, globalThis should not be abused. You should still keep as much of your code outside global scope, inside functions and code blocks.

The globalThis should be used mainly for things such as polyfills and shims. globalThis could be also used is feature detection, to detect which JavaScript features are supported in a specific browser or environment.

// In the browser
globalThis === window
// true

const obj = {
  name: 'foo',
  getThis: function() {
    return this
  },
  getGlobalThis = function() {
    return globalThis
  }
}
obj.getThis()
// {name: "foo", getThis: ƒ}
obj.getGlobalThis()
// Window { ... }
// In Node.js
globalThis === global
// true

#javascript #web development #javascript development #es2020

How “this” in JavaScript Works - Things Need to Notice
18.45 GEEK