[2017-12-06] Follow-up post: “A different way of understanding `this` in JavaScript”

In JavaScript, the special variable this is relatively complicated, because it is available everywhere, not just in object-oriented settings. This blog post explains how this works and where it can cause problems, concluding with best practices.

To understand this , it is best to partition the locations where it can be used into three categories:

In functions: this is an extra, often implicit, parameter.

is an extra, often implicit, parameter. Outside functions (in the top-level scope): this refers to the global object in browsers and to a module’s exports in Node.js.

refers to the global object in browsers and to a module’s exports in Node.js. In a string passed to eval() : eval() either picks up the current value of this or sets it to the global object, depending on whether it is called directly or indirectly.

this in functions

this

Real functions ( this is the global object in sloppy mode, undefined in strict mode)

is the global object in sloppy mode, in strict mode) Constructors ( this refers to the newly created instance)

refers to the newly created instance) Methods ( this refers to the receiver of the method call)

this

this in real functions

this

Sloppy mode: this refers to the global object ( window in browsers). function sloppyFunc() { console.log(this === window); // true } sloppyFunc();

refers to the global object ( in browsers). Strict mode: this has the value undefined . function strictFunc() { 'use strict'; console.log(this === undefined); // true } strictFunc();

this

window

undefined

call()

apply()

this

function func(arg1, arg2) { console.log(this); // a console.log(arg1); // b console.log(arg2); // c } func.call('a', 'b', 'c'); // (this, arg1, arg2) func.apply('a', ['b', 'c']); // (this, arrayWithArgs)

this in constructors

new

this

var savedThis; function Constr() { savedThis = this; } var inst = new Constr(); console.log(savedThis === inst); // true

new

function newOperator(Constr, arrayWithArgs) { var thisValue = Object.create(Constr.prototype); Constr.apply(thisValue, arrayWithArgs); return thisValue; }

this in methods

this

var obj = { method: function () { console.log(this === obj); // true } } obj.method();

this in the top-level scope

this

window

<script> console.log(this === window); // true </script>

// `global` (not `window`) refers to global object: console.log(Math === global.Math); // true // `this` doesn’t refer to the global object: console.log(this !== global); // true // `this` refers to a module’s exports: console.log(this === module.exports); // true

this in eval()

eval()

Let’s examine each of these categories.That’s the most common way of using, because functions represent all callable constructs in JavaScript, by playing three different roles:In functions,can be thought of as an extra, often implicit, parameter.In real functions, the value ofdepends on the mode one is in:That is,is an implicit parameter that is set to a default value (or). You can, however, make a function call viaorand specify the value ofexplicitly:Functions become constructors if you invoke them via theoperator. That operator creates a new object and passes it to the constructor viaImplemented in JavaScript, theoperator looks roughly as follows (a more accurate implementation is slightly more complex ):In methods, things are similar to more traditional object-oriented languages:refers to the, the object on which the method has been invoked.In browsers, the top-level scope is the global scope andrefers to the global object (likedoes):In Node.js, you normally execute code in modules. Therefore, the top-level scope is a specialcan be called either(via a real function call) or(via some other means). The details are explained here

If eval() is called indirectly, this refers to the global object:

> (0,eval)('this === window') true

eval()

this

eval()

// Real functions function sloppyFunc() { console.log(eval('this') === window); // true } sloppyFunc(); function strictFunc() { 'use strict'; console.log(eval('this') === undefined); // true } strictFunc(); // Constructors var savedThis; function Constr() { savedThis = eval('this'); } var inst = new Constr(); console.log(savedThis === inst); // true // Methods var obj = { method: function () { console.log(eval('this') === obj); // true } } obj.method();

this -related pitfalls

this

this

undefined

Pitfall: forgetting new

new

this

this

window

function Point(x, y) { this.x = x; this.y = y; } var p = Point(7, 5); // we forgot new! console.log(p === undefined); // true // Global variables have been created: console.log(x); // 7 console.log(y); // 5

this === undefined

function Point(x, y) { 'use strict'; this.x = x; this.y = y; } var p = Point(7, 5); // TypeError: Cannot set property 'x' of undefined

Pitfall: extracting methods improperly

setTimeout()

callIt()

/** Similar to setTimeout() and setImmediate() */ function callIt(func) { func(); }

this

var counter = { count: 0, // Sloppy-mode method inc: function () { this.count++; } } callIt(counter.inc); // Didn’t work: console.log(counter.count); // 0 // Instead, a global variable has been created // (NaN is result of applying ++ to undefined): console.log(count); // NaN

this

undefined

var counter = { count: 0, // Strict-mode method inc: function () { 'use strict'; this.count++; } } callIt(counter.inc); // TypeError: Cannot read property 'count' of undefined console.log(counter.count);

var counter = { count: 0, inc: function () { this.count++; } } callIt(counter.inc.bind(counter)); // It worked! console.log(counter.count); // 1

bind()

this

counter

Pitfall: shadowing this

this

this

var obj = { name: 'Jane', friends: [ 'Tarzan', 'Cheeta' ], loop: function () { 'use strict'; this.friends.forEach( function (friend) { console.log( this.name +' knows '+friend); } ); } }; obj.loop(); // TypeError: Cannot read property 'name' of undefined

this.name

this

undefined

this

loop()

Otherwise, ifis called directly,remains the same as in the surroundings of. For example:There are three-related pitfalls that you should be aware of. Note that in each case, strict mode makes things safer, becauseisin real functions and you get warnings when things go wrong.If you invoke a constructor and forget theoperator, you are accidently using it as a real function. Hence,does not have the correct value. In sloppy mode,isand you’ll create global variables:Thankfully, you get a warning in strict mode ():If you retrieve the value of a method (instead of invoking it), you turn the method into a function. Calling the value results in a function call, not a method call. This kind of extraction can happen when you pass a method as an argument for a function or method call. Real-world examples includeand registering event handlers. I’ll use the functionto simulate this use case synchronously:If you call a sloppy-mode method as a function,refers to the global object and global variables will be created:If you call a strict-mode method as a function,is. Things don’t work, either. But at least you get a warning:The fix is to use bind() created a new function that always receives awhose value isWhen you use a real function inside a method, it is easy to forget that the former has its own(even though it has no need for it). Therefore, you can’t refer from the former to the method’s, because it is shadowed. Let’s look at an example where things go wrong:In the previous example,fails, because the function’sis, it is not the same as theof the method. There are three ways to fix … this.

Fix 1: that = this . Assign this to a variable that isn’t shadowed (another popular name is self ) and use that one.

loop: function () { 'use strict'; var that = this; this.friends.forEach(function (friend) { console.log( that .name+' knows '+friend); }); }

Fix 2: bind() . Use bind() to create a function whose this always has the correct value (the method’s this in the following example).

loop: function () { 'use strict'; this.friends.forEach(function (friend) { console.log(this.name+' knows '+friend); } .bind(this) ); }

Fix 3: forEach ’s second parameter. This method has a second parameter whose value is passed to the callback as this .

loop: function () { 'use strict'; this.friends.forEach(function (friend) { console.log(this.name+' knows '+friend); } , this ); }

Best practices

this

this

this

loop: function () { 'use strict'; // The parameter of forEach() is an arrow function this.friends.forEach(friend => { // `this` is loop’s `this` console.log(this.name+' knows '+friend); }); }

this

beforeEach(function () { this .addMatchers({ toBeInRange: function (start, end) { ... } }); });

beforeEach( api => { api .addMatchers({ toBeInRange(start, end) { ... } }); });

Conceptually, I think of real functions as not having their ownand think of the aforementioned fixes as keeping up that illusion. ECMAScript 6 supports this approach via arrow functions – functions without their own. Inside such functions, you can freely use, because there is no shadowing:I don’t like APIs that useas an additional parameter of real functions:Turning such an implicit parameter into an explicit one makes things more obvious and is compatible with arrow functions.