
28 Nov 2018 This (and why it’s so weird in JavaScript)
A lot of people come to JavaScript from languages like Java. And, at first glance, the languages seem kind of similar – they even have similar names! Of course, it soon becomes apparent that there are some pretty important differences. For example, Java is object-oriented, statically typed and lexically scoped. JavaScript isn’t these things (except sometimes, when it is). And one of the places that these differences are really obvious is in the use of the keyword this
.
the adventures of this and the global scope
One of the big differences between JavaScript this
and this
in Java (and I assume other, more sane languages), is that in JavaScript you can use this
outside of a class.
this.foo = 42;
is totally legal anywhere in JavaScript. What does this
refer to in this case? Well, it depends.
In non-strict (or sloppy) mode, a global this
refers to the window
object. (If you’re not familiar with strict/non-strict mode, see Strict mode on MDN.) Your browser console runs in non-strict mode by default:
>> this Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
In strict mode, however, things are a bit different – this
is undefined in the global scope:
>> (function () { 'use strict'; console.log(this) })() undefined undefined
(I’m using an immediately invoked function expression here so I can force strict mode. If you’re not familiar with them, see IIFE on MDN. Why does the console print undefined
twice? The first is our console.log
. The second is the return value of the IIFE.)
ES6 modules run in strict mode by default, so if you’re writing modern code, you’re probably in strict mode, but it’s not always obvious.
Moral of the story? As we all learned from the events of Jurassic Park, just because you can do a thing doesn’t mean that you should do that thing, so
Don’t use this
in a global scope
this, like, literally
A super-handy feature of the JavaScript is the ability to create object literals. As you might expect, if you use this
within an object, it refers to the object itself.
const velociraptor = { location: 'raptor_enclosure', hasEscaped: () => this.location !== 'raptor_enclosure' }
Great. We know whether or not our raptor has escaped.
The one exception to this is if we use this
in our initial definition.
const files = { directory: 'src/app', htmlFiles: `${this.directory}/**/*.html`, testFiles: `${this.directory}/**/*.spec.ts` }
Logging files.htmlFiles
will give us undefined/**/*.html
, which is almost certainly not what we wanted.
Why does this happen? Well, at the point we’re using this
, we haven’t actually finished creating the object yet, so there’s nothing for this
to refer to.
So, using this
inside object literals:
- inside a function is fine 🎉
- to define a property, doesn’t work 😿
lexical scope & dynamic scope
This is where the fun really begins with this
in JavaScript. In most (sensible) languages, this
is lexically scoped. That means that this
refers to the thing that it looks like it refers to when you write the code. It just works the way you would expect.
class SomeJavaClass { boolean worksAsExpected; SomeJavaClass() { this.worksAsExpected = true; // this refers to the current instance of this class, as expected } }
JavaScript doesn’t work that way. In JavaScript, what this
refers to depends entirely on where it’s called from.
const object1 = { expected: true, worksAsExpected: function () { return this.expected; }, }; const object2 = { expected: false, worksAsExpected: object1.worksAsExpected, }; object1.worksAsExpected(); // true object2.worksAsExpected(); // false
Even though the worksAsExpected
function is declared as part of object1
, when we use it in object2
, this
refers to object2
. It might help to remember that functions in JavaScript are objects in their own right. When you declare a function inside an object or class, you’re really creating a Function object and giving your object a reference to that Function. Another object can also get a reference to that Function. Neither object “owns” the function, they just both have references to it.
This is going to come back to bite you in situations where you need to pass a callback, and you pass a callback that belongs to an object. Common cases are setTimeout
, Array.prototype.forEach/some/every/find/include
, and Promise
callbacks. So how do you deal with it? There are two options.
The “better” way is to use Function.prototype.bind
. The first argument of bind
specifies the object that you want this
to refer to when the function is called. (You can also specify other arguments to bind to the parameters of the function.) bind
returns a new function, which has this
bound to the specified object.
const object1 = { expected: true, worksAsExpected: function () { return this.expected; }, }; const object2 = { expected: false, worksAsExpected: object1.worksAsExpected.bind(object1), }; object1.worksAsExpected(); // true object2.worksAsExpected(); // true
Binding via Function.prototype.bind
is (almost) the strongest kind of binding there is. You can’t override it by calling the function in a different location. (You can sort of override it by calling bind
again, but when you do that, you’re really just getting another function with a different this
). You also can’t override it using Function.prototype.call
or Function.prototype.apply
. If you’re not familiar with call
and apply
, they work similar to bind
. The difference is that bind
returns a new function that you can call later. call
and apply
actually call the function immediately (hence the name, I suppose). As such, they’re a bit less useful than bind
.
The other way to work around dynamic binding is to use arrow functions. Arrow functions, unlike the rest of JavaScript, use lexical binding.
const object1 = { expected: true, worksAsExpected: () => this.expected, }; const object2 = { expected: false, worksAsExpected: object1.worksAsExpected, }; object1.worksAsExpected(); // true object2.worksAsExpected(); // true
Arrow functions are handy when you want an anonymous function that is lexically scoped. I generally prefer arrow functions when I’m passing a callback inline, and bind
when I’m passing in a named function. (bind
also has the nice effect of making it explicit what you’re doing. The fact that arrow functions are lexically scoped is easy to forget and can lead to unexpected bugs).
this.amount = 4; fetch(priceUrl) .then(response => response.json()) .then(price => this.total = price * this.amount) *************************** setTimeout(this.onTimeout.bind(this), delay);
There’s also no way to override the lexical binding of an arrow function – JavaScript won’t let you call bind
on them.
why would JavaScript even do that?
At this point, I’m guessing you’re probably wondering why have dynamic scope at all? Wouldn’t it just be easier to have lexical scope like a normal language? Well, yes and no. There are common tasks in JavaScript that are made easier by being able to use dynamic scope. For example, event handlers! The code below could be used for adding an animation to a button when it’s clicked (like the Material Design ink ripple), and it’s super handy to be able to refer to the target element using this
. Dynamic scoping means I can use this same handler across multiple buttons.
const button = document.querySelector('button'); button.addEventListener('click', function (event) { this.classList.add('ripple'); this.addEventListener('transitionend', () => this.classList.remove('ripple'); });
The thing is, when you’re writing code in a framework, you’re generally not touching the DOM like this. So you’re not really seeing the advantages of dynamic binding. But trust me – other people are!
conclusion
So, the things to remember about this
in JavaScript:
- Don’t use
this
in the global scope. Just don’t. - When defining a property in an object literal,
this
refers to the parent scope. (In a function,this
refers to the object) this
is dynamically scoped, but you can bindthis
within a function usingFunction.prototype.bind/call/apply
.bind
is the strongest binding and can’t be overriddenthis
is lexically scoped within arrow functions
bonus javascript weirdness
Haven’t had enough? Well, there is one other really weird thing JavaScript does with this
, which is almost useless to know about. So let me tell you all about it! Remember before when I said bind
was (almost) the strongest way to set what this
refers to in a function? There is one way that is stronger – constructor this
!
Unlike more object-oriented languages, JavaScript doesn’t really have the concept of a constructor function. (Yes, it exists in ES6 classes, but those are just syntactic sugar). Instead, you can call any function with the new
operator, and it magically becomes a constructor.
When you call a function with the new
operator, a brand new object gets created and this
inside the function now refers to that object. This new object is returned by the function.
function printDino() { console.log(this.dino); } const boundPrintDino = printDino.bind({ dino: 'raptor' }); console.log(boundPrintDino()); // raptor console.log(new boundPrintDino()); // undefined
If you run this code in a console, you’ll also get a line that looks something like
>> printDino {}__proto__: Object
This is the new object returned by new printDino.
You can override this behaviour by returning an object from your constructor. If you do that, then this
inside your constructor is still going to refer to the new object created when the constructor was called. But you now have no way of accessing that object.
class MySillyClass { constructor() { this.foo = 42; return { bar: 88 }; } } const mySillyObject = new MySillyClass(); console.log(mySillyObject.foo); // undefined console.log(mySillyObject.bar); // 88
Is this helpful? Not really. It’s just kind of a holdover from the pre-classy days of JavaScript.
No Comments