Table of Contents
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.target
, event.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 window
, self
, this
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
Conclusion: How “this” in JavaScript Works
You’ve just reached the end of this tutorial. I hope you enjoyed it. I also hope that it helped you understand how this
keyword works and how to use it. The this
keyword can be very confusing and it may take time to understand it. However, it is worth the time. When you understand it, you will also better understand JavaScript itself.
If you liked this article, please subscribe so you don't miss any future post.
If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂