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.
#
ObjectivesBy 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
this
in different calling contexts. - Use
apply
,call
, andbind
to 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
this
is equivalent towindow
.
#
In Node.js- The top-level scope is not the global scope.
- Node does have a global variable named
global
and is documented here.
GOTCHA Global variables, methods, or functions can easily create name conflicts and bugs in the global object.
this
Changes by Call Context#
Demo: 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 InvocationWe 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 PatternWhen 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'
this
in global functions#
Lab: 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 PatternWhen 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 chainingLet'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 PatternRemember 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 PatternFunction 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.
.call
#
Demo: Using 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)".
this
#
Fat arrow functions and 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) }}
this
#
Lab: binding 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
,this
will be the constructed object. - In a function invoked with
.call
or.apply
,this
will be the first argument passed to call or apply. - In a function created with
.bind
,this
will be the first argument passed to.bind
. - In a (non-fat arrow) function declared as a method on an object,
this
will be the object on which the method is declared. - If none of the above apply,
this
will be either the global object, orundefined
depending on whether'use strict'
is enabled.