Understanding the JavaScript this keyword
In JavaScript the value that the this
keyword refers to changes depending on the current execution context. The execution context is the environment in which a line is being executed, the JavaScript interpreter maintains a stack of these contexts and updates the object that this
references to keep it current.
Global Execution Context
Outside of any function, this refers to the global object. In web browsers, the window object is also the global object. In
a Node.js environment it will refer to a special object called global
.
console.log(this === window);
// true
Function Execution Context
When you use this
inside of a function it's value will depend on how you are calling that function.
function foo() {
console.log(this === window);
}
foo();
// true
Immediately invoked function expressions behave the exact same.
(function foo() {
console.log(this === window);
})();
// true
If strict mode is enabled for any function, then the value of this
will be undefined
.
function foo() {
'use strict';
console.log(this === window);
console.log(this === undefined);
}
foo();
// false (this !== window)
// true (this === undefined)
Every function in JavaScript has call
, bind
and apply
methods that can be used to set a custom value for this
.
const custom = { message: 'Hello Universe!' };
const message = 'Hello World!';
function logThisMessage() {
console.log(this.message);
}
logThisMessage();
// Hello World!
logThisMessage.call(custom);
// Hello Universe!
logThisMessage.apply(custom);
// Hello Universe!
You may have noticed that both call and apply are doing the same thing in the above example, the difference between them is how you would pass additional arguments to function.
const custom = { message: 'Hello Universe!' };
const message = 'Hello World!';
function logThisMessage(arg1, arg2) {
console.log(`${this.message} total: ${this.arg1 + this.arg2}`);
}
logThisMessage(3, 2);
// Hello World! total: 5
logThisMessage.call(custom, 3, 2);
// Hello Universe! total: 5
logThisMessage.apply(custom, [3, 2]);
// Hello Universe! total: 5
Bind behaves a little bit differently, it creates a new function with the same body and scope as the function you're calling it on but where this
occurs in original function, it is now permanently bound to the first argument of bind.
function logThisMessage() {
console.log(this.message);
}
const helloWorld = logThisMessage.bind({ message: 'Hello Universe!' });
helloWorld();
// Hello Universe!
const myObj = {
message: 'Hello World!',
logThisMessage: logThisMessage,
helloWorld: helloWorld,
};
console.log(myObj.a);
// Hello World!
myObj.logThisMessage();
// Hello World!
myObj.helloWorld();
// Hello Universe!
With arrow functions the value of this doesn't change and instead keeps referencing the object for the enclosing lexical context.
const foo = () => console.log(this === window);
foo();
// true
In this example the enclosing lexical context remains the global context meaning this references the window object.
Class Execution Context
When we create a new instance of our class using the new
keyword, this
refers to the newly created instance.
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
displayMakeModel() {
console.log(`Make: ${this.make}, Model: ${this.model}`);
}
}
const car = new Car('Ford', 'Focus');
car.displayMakeModel();
// Make: Ford, Model: Focus
const anotherCar = new Car('Fiat', 'Punto');
anotherCar.displayMakeModel();
// Make: Fiat, Model: Punto
Because JavaScript classes are really just functions under the hood you can use the call
, bind
and apply
methods on them as well.