JavaScript: this
The keyword this in JavaScript can be a major point of a confusion and a
source of tricky bugs. Let's dig into what it references in different contexts.
Objectives
By the end of this, developers should be able to:
- Explain the difference between a value being determined at declaration versus runtime.
- Read and follow the execution of code that uses
thisin different calling contexts. - Use
apply,call, andbindto explicitly set the value ofthis.
this Is A Reference
We use this similar to the way we use pronouns in natural languages like English and French. We write: “John is running fast because he is trying to catch the train.” Note the use of the pronoun “he.” We could have written this: “John is running fast because John is trying to catch the train.” We don’t reuse “John” in this manner, for if we do, our family, friends, and colleagues would abandon us. Yes, they would. In a similar aesthetic manner, we use the this keyword as a shortcut, a referent to refer to an object. Source: Understanding Javascript 'this' pointer.
this in the Global Scope Depends on the Environment
In browsers
- The top-level scope is the global scope.
- In the top-level scope in browsers
thisis equivalent towindow.
In Node.js
- The top-level scope is not the global scope.
- Node does have a global variable named
globaland is documented here.
GOTCHA Global variables, methods, or functions can easily create name conflicts and bugs in the global object.
Demo: this Changes by Call Context
A function can indiscriminately operate upon any object. When a function is
invoked, it is bound to an object on which it operates. The contextual object
on which a function operates is referenced using the keyword this.
Watch as I run the following example in Node. What will each instance of this
refer to at runtime?
const rocket = {
destination: null,
setDestination: function (planet) {
this.destination = planet;
this.blastOff();
},
blastOff: function () {
console.log(`VRROOOMM!! Off to ${this.destination}!`);
},
};
rocket.setDestination("Mars"); // What will this log?
The Four Patterns of Invocation
We must invoke a function to run it (ie: call upon the function to do its thing). Amazingly, there are FOUR ways to invoke a function in JavaScript. This makes JS both amazingly flexible and absolutely insane.
Function Invocation Pattern
When a function is invoked without explicit context, the function is bound to global scope:
const goBoom = function () {
console.log("this is ", this);
};
goBoom();
// what logs in the browser vs in node?
Following best practices, we can add use strict to get consistent results
"use strict";
const goBoom = function () {
console.log("this is ", this);
};
goBoom(); // what logs in the browser vs in node?
Context: this refers to the window object (global scope). Here we
would say "a method is called on an object". In this case the object is the
window.
Gotcha: This behavior has changed in ECMAScript 5 only when using strict
mode: 'use strict'
Lab: this in global functions
Take a look at the following code, then run it.
"use strict";
const printThis = function () {
console.log("In a function in the global scope, this is:", this);
};
printThis();
Next, paste that function into the Node REPL and invoke it. Is the output the same? Why or why not?
Method Invocation Pattern
When a function is defined on an object, it is said to be a method of the object. When a method is invoked through its host object, the method is bound to its host:
const alien = {
contact: function () {
console.log("We are:", this);
console.log("We come in peace.");
},
};
alien.contact();
// this === alien
Context: this refers to the host object.
Lab: method chaining
Let's take advantage of this invocation pattern to implement methods that can
be chained on the object that they're called on. Open up lib/method-chain.js
and change the satellite object so that the method invocation at the bottom
of the file logs the message three times.
Constructor Invocation Pattern
Remember constructor functions from unit 1? Any function may act as a
constructor for new object instances. New object instances may be constructed
with the new keyword while invoking a function.
Constructors are very similar to Ruby class constructors, in that they represent proper nouns within our application. Therefore they should follow the convention of capitalized names.
const Planet = function (name, color) {
console.log("this is ", this);
this.name = name;
this.color = color;
};
const mercury = new Planet("Mercury", "slightly brownish");
const pluto = new Planet("Pluto", "blue");
// this === shiny new Planet instance
Context: this refers to the newly-created object instance. Here we
would say "the object receives the method".
How this breaks down:
- Creates a new empty object
// {} - Attaches the constructor to the object as a property
// {}.constructor = Planet - Invokes the constructor function on the new object
// {}.constructor('???') - Returns the object
// {}
Call/Apply Invocation Pattern
Function objects have their own set of native methods, most notably are
.call
and .apply.
These methods will invoke the function with a provided
contextual object.
While the syntax of these functions are almost identical,
the fundamental difference is that call() accepts an argument list,
while apply() accepts a single array of arguments.
You won't need to use these methods often, but it's good to know they exist.
Demo: Using .call
Take a look at the following code. What do you think will happen when we run it?
"use strict";
const personOne = {
firstName: "John",
lastName: "Doe",
fullName: function () {
console.log(this.firstName + " " + this.lastName);
},
};
const personTwo = {
firstName: "Mary",
lastName: "Smith",
};
personOne.fullName.call(personTwo); // What will this print?
Context: this refers to the passed object. Here you would say
"Call the method fullName with personTwo as the context (this)".
Fat arrow functions and this
ES6 arrow functions behave differently with regards to this. Specifically,
this inside a fat arrow function will refer to whatever this would be
outside of the arrow function's scope. In other words, arrow functions don't
have "their own" this.
"use strict";
const blackHole = {
escaped: "Whew! Our heroes escaped the black hole.",
tryToEscape: () => {
console.log(this.escaped);
},
};
blackHole.tryToEscape();
// undefined, because `this` in global functions is undefined in strict mode
So why would we use fat arrow functions? Consider the following situation:
const counterObj = {
value: 0,
increment: function () {
setTimeout(function () {
this.value++
console.log(this.value)
// ERROR: `this` is the callback function, not `counterObj`
}, 5000)
}
}
const otherCounterObj = {
value: 0,
increment: function () {
setTimeout(() => {
this.value++
console.log(this.value)
// SUCCESS: `this` has the value of `otherCounterObj`
// `this.value` evaluates to `otherCounterObj.value`
}, 5000)
}
}
Lab: binding this
JavaScript also provides a method called .bind, which lets us create a new
function that is identical to an existing function, except that this will be
permanently set to whatever we want!
We can use .bind like this:
const moon = { name: "The Moon" };
const mars = { name: "Mars" };
const rocket = {
blastOff: function () {
return `We are blasting off to ${this.name}!`;
},
};
const moonBlast = rocket.blastOff.bind(moon);
const marsBlast = rocket.blastOff.bind(mars);
console.log(moonBlast()); // 'We are blasting off to The Moon!'
console.log(marsBlast()); // 'We are blasting off to Mars!'
Read up a bit on .bind here,
then open lib/bind.js and use .bind to save these poor astronauts from the
black hole they've found their way into.
this cheatsheet
How to determine what this refers to:
- In a function invoked with
new,thiswill be the constructed object. - In a function invoked with
.callor.apply,thiswill be the first argument passed to call or apply. - In a function created with
.bind,thiswill be the first argument passed to.bind. - In a (non-fat arrow) function declared as a method on an object,
thiswill be the object on which the method is declared. - If none of the above apply,
thiswill be either the global object, orundefineddepending on whether'use strict'is enabled.