July 17, 2019
Share this:
JavaScript Beginner

3 Techniques for Maintaining Your Sanity Using "This" in JavaScript

Of JavaScript's many confusing aspects, the keyword this can be one of the most complicated -- Here's a joke about the troublesome keyword:

this is frustrating. Every time you think you have it, another weird case shows up - it should be simple, so why does it never seem to work like you want it to?

Why "this" is confusing

In other programming languages, this always refers to the current instance of an object. It's a very consistent keyword which will only ever hold two values: the current object, or nothing.

In JavaScript, this refers to what is known as the execution context. In practical contexts, this is deceptively similar to other languages version of this, but contains a fundament difference: the execution context is different based on how a function is called.

This means that JavaScript's version of this may have different values depending on how you called the function.

class Foo {
	text = "string";

	trigger() {
		// Because of how the function is being called, `this` can have
		// several different values
		this.text = this.text + "a";
	}

	brokenTrigger() {
		// `this` will refer to the current object, so it will act as we expect
		this.trigger();

		// setTimeout resets `this` to the global context object - in web
		// browsers, it is the Window object
		setTimeout(this.trigger, 500);

        	// When we refer to the function directly (without the object)
		// `this` refers to the global context object (window)
        	const unboundFunction = this.trigger;
        	unboundFunction();

        	// Event listeners replace "this" with the target element 
        	// `this` will refer to the clicked ".triggerButton"
        	let button = document.querySelector(".triggerButton");
        	button.addEventListener('click', this.trigger);
	}
}

How to use this safely

When you see all the ways that this can go wrong, it seems like the easiest option is to throw your hands in the air, become a hermit and start a small potato farm.

In practice, this tends to be far less problematic than these examples make it appear. Most of the weird behaviours of this are easy to avoid by restricting your use of this to object functions, where it is the most consistent

As I said in the intro, using this with an object is almost always going to refer the object instance, but you do have to watch for two major exceptions:

  1. setTimeout
  2. addEventListener

In these cases, we have several techniques at our disposal to control the value of this, and to make sure that it works how we want.

Technique 1: Use Fat Arrow Functions

Fat Arrow Functions, aside from being a quick way of declaring functions, differ slightly from other function declarations in that they won't allow anything to overwrite this. Instead, it keeps the value from where the function is declared (its lexical scope).

What this means is that we can use them as wrappers, or directly as event listener function calls to preserve our this reference.

class Foo {
	listen() {
		// `this` still refers to Foo
		document.querySelector('.class').addEventListener('click', (e) => {
			this.handler(e); 
			// or
			this.val = 1;
        	});
	}

	handler(e) {
		this.val = 1;
	}
}

Technique 2:  Assign this to a variable

Before ES6, a popular pattern was to copy the value of this when we knew it referred to our object and used the new variable instead.

var foo = {
	listen: function() {
		// These are both common names for our new `this`
		var that = this;
		var self = this;

		document.querySelector('.class').addEventListener('click', function() {
			self.val = 1;
		});
	}
}

Technique 3: Explicitly set this with Function.bind

Functions come with several tools to set the value of this explicitly so you can guarantee the value of this.

In practice, Function.bind is the most useful of the three, since it doesn't immediately call the function, instead returning a new version with a pre-set this, and any parameters you pass - you can use this new function directly in setTimeout or addEventListener function and keep your value of this.

class Foo {
	listen() {
		// The first paramter of `bind` is the new `this` value
		document.querySelector('.class').addEventListener('click', this.handleEvent.bind(this));
	}

	handleEvent() {
		this.val = 1;
	}
}

Bonus Technique: Use Strict Mode

JavaScript's Strict Mode slightly changes the behaviour of this. Instead of implicitly setting this to the global context outsides of objects, it causes it to be undefined instead.

In practical terms, this is a fairly minor change, but it prevents several incorrect usages of this, and cause a would-be-hidden bug to throw an error instead:

'use strict';
let obj = {
	update(val) {
		// Normally this will create an `x` property on the global object and
		// continue running, but in strict mode this will throw an error
		this.x = val;
	}
}

// Breaking the reference to `obj` causes `this` to be undefined instead
// of referring to the global object
let func = obj.update;
func();

Don't overcomplicate it

If you regularly read my blog, you'll know this is basically my slogan.

There's no denying that this is strange, but that doesn't mean that you need to worry about all the edge cases that it presents - More often than not, they don't come up.

I've been a Web Developer for coming on 8 years now, and I learned some new edge cases about this when preparing this post that I have never encountered before.

Instead of spending your time memorising every way this can go wrong,  start learning a framework, or get out there are create some practice projects to get a better sense of how it works.

If you want to read more about some JS fundamentals, you can check out this guide to closures.

Share this: