On `this` in Javascript

Nobody listens to me.

I’ve been saying for years that the constructor pattern, any use of the new keyword or function constructors in Javascript should be considered extremely advanced and not generally worth the rise in complexity in your code. I’ve been telling people about simple objects, and avoiding the this keyword. I’ve been trying to spread that gospel far and wide. But Javascript went ahead anyways and introduced the class keyword, ReactJs compounds their jsx mistake by recommending inheritance-based component syntax, and I keep helping dozens upon dozens of beginners fix their broken code by explaining patiently the correct incantation of symbols that is needed to get their functions to bind properly when really they just needed functions, objects, and variables.

Nobody listens to me.

So lets just go ahead and reset. I’ll write this up once and for all, people can read it, and if there’s any questions…well hell, I’m in 10 different Slacks and a bunch of IRCs, I’m not exactly hard to track down.

Let’s understand the this keyword

This is actually a more narrow subject than the whole discussion on constructors and new, but the latter is predicated on it, so lets start here.

Burn this into your brain: In javascript, this is just a function parameter that you don’t get to name.

Its a mind-warp so let me try and guide you through it.

Talking regular functions

Lets take the time machine back to 2013 when arrow functions were just a coffeescript thing and the only way we had of creating a function in javascript is with the gross long-form function keyword.

Now lets say we have a function declared as so

function sayHi( fromPerson, toName ) {
  console.log( fromPerson.name + " says hi to " + toName )
}

We can invoke that like this

var me = {
  name: "George",
};

sayHi( me, "Donna" ); // logs "George says hi to Donna"

many know that we can also do

sayHi.call( null, me, "Donna" ); // also logs "George says hi to Donna"

In fact, in many ways, the first form is just shorthand for the second. So lets simplify things. For the sake of conversation, let’s assume all functions are invoked with .call()

How .call() works

Ok, then, if we’re getting so specific, then let’s take the time to understand things fully. What is with that null? Why are we passing a null argument? if argument 2 binds to the fromPerson parameter, and argument 3 binds to the toName parameter, what on earth does argument 1 bind to?

Well it turns out, there’s another “secret” parameter inside your function. We all get how foreshadowing works: It’s named this

function sayHi( fromPerson, toName ) {
  console.log( "this is ", this)
  console.log( fromPerson.name + " says hi to " + toName )
}

And in a twist that I’m sure you all saw coming, sayHi.call( null, me, "Donna" ) will notify us that "this is null" and sayHi.call( "abc", me, "Donna" ) that "this is abc".

I guess then we can rewrite our function

function sayHi( toName ) {
  console.log( this.name + " says hi to " + toName )
}

And call it with sayHi.call( me, "Donna" ) and that works fine.

So if the above is true, then I ask you - in what way is this different from any other parameter? The answer is that it s really not! It’s a parameter like the others, you simply do not get to give it a nice descriptive name.

And if we only ever invoked our functions using .call() syntax that would be the end of the story, but we have the sayHi() shorthand - so what exactly does it do?

How invocation() works

Here are the rules on standard invocation()

  • By default this will be the global object (window in the browser, global in node)
  • Unless you are running in strict mode (almost always a good idea) and 'use strict' is somewhere in sayHi’s scope chain - then this will be undefined

So we have

sayHi( me, "Donna" ) // " says hi to Donna" (usually, window.name is an empty string)

// but if you have somewhere in the context chain of sayHi
'use strict'
sayHi( me, "Donna" ) // throw TypeError

Ok, well that’s weird, but not all that confusing. Is there more to the story? Why yes, yes there is!

So anyone who’s worked with javascript objects knows about the dot operator foo.bar gets the object in the variable foo and then gets the value in it’s property bar.

But it does a bit more than that when combined with invocation

foo.doBar()

doesn’t merely say

Get the object in variable foo, get the value in property doBar, then, assuming its a function, invoke it

The dot operator actually modifies the invocation to the right to say that the thing to the left of the dot operator is set as this. So the above is the equivalent of foo.doBar.call(foo).

Ok, this is getting more wild. Lets do some more examples.

me.greet = sayHi

me.greet( "Donna" ) //"George says hi to Donna" (dot operator modifies invocation)

const greet2 = me.greet
greet2( "Donna" ) //" says hi to Donna" (identical to sayHi("Donna"). no dot and invocation, this is the global object)

const you = { name: "Bob" }
me.greet.call( you, "Donna" ) // "Bob says hi to Donna"

function callWithDonna( fn ) {
   fn( "Donna" )
}
callWithDonna( sayHi ) //" says hi to Donna"
callWithDonna( me.greet ) //" says hi to Donna" (no dot and invocation together - identical to the above)
callWithDonna( function(toName) { me.greet(toName) }) //"George says hi to Donna"

aaaaaaalllllriiiight….I mean thats pretty crazy, but it kinda makes sense. Are there any more wrinkles?

Oh you know that there are.

Talking arrow functions

Now I’ll say this: arrow functions are a great addition to the language. But the above situation, combined with common misinformation about how they work has not made things any easier.

So an arrow function is just like a regular function, except that it does not have a secret this variable (or secret arguments, super, or new.target variables, but that’s outside the scope of this article).

But hold on, you say, you’ve definitely used this inside arrow functions! Isn’t there something about arrows giving this lexical scope?

No, no there is not - people don’t know what they are talking about if they tell you that. this has always had lexical scope. Its not function scope, and its not dynamic scope, what on earth else would it be?

Instead, the rules on all variables named this in an arrow function are….well..pretty much the same as the rules on all other variables.

function sayHi( toName ) {
   const greeting = "hi";

   const greet = () => {
     console.log( this.name + " says " + greeting + " to " + toName );
   };

   greet();
}

me.greet = sayHi;
me.greet( "Donna" ); //"George says hi to Donna"

So what happens when the above arrow function references this? Well what happens when it references greeting? Neither exist in the scope of the arrow function, so it looks up a scope to the sayHi function which has both! So this becomes the object in me and this.name becomes "George".

So what about .call() do arrow functions work with it? They do indeed! But since arrow functions don’t have a this keyword, the first argument in is summarily ignored.

Ok, well…ugh…sure…fine. I guess. So what are some effects of all of….this….

A look at binding in classes

Now I’m not going to get into all the rules on classes, constructors, and the new keyword here. But lets take what we learned above and how it affects some code you might commonly see.

Well lets say we have a class containing some sort of a “component” structure

class App {
  constructor( props ) {
    this.nodeType = "People list";
    this.names = ["George", "Bob", "Donna", "Erica"];
  }

  render( el ) {
    const ul = document.createElement('ul')
    this.names
        .map( this._renderListItemName )
        .forEach( li => ul.appendChild(li) );

    el.innerHTML = ``;
    el.appendChild(ul);
  }

  _renderListItemName( name ) {
    const li = document.createElement('li');
    li.textContent = name;
    li.addEventListener( 'click', this._logClick );
    return li
  }

  _logClick() {
    alert( `You have clicked on a part of ${this.nodeType}` )
  }
}

const app = new App();
const body = document.querySelector('body');
app.render( body );

Got it?

Take some time and look at it and see if you can spot some of the problems. One error will prevent anything from rendering due to a thrown error, another - even when the correct code is triggered - will cause clicks to confusingly refer to a number, not "People list"

Play around with it here and try to fix things

An executable sample on jsbin


Got your guesses in? Ok.

The first error is due to in this.names.map( this._renderListItemName ) but confusingly throws an error elsewhere entirely! Remember that _renderListItemName is a function - and we are giving it to .map() to call at will. We are at the mercy of .map() to call it correctly in a way that passes the this parameter properly - and .map() simply has no idea that its supposed to do that. Nor - if you think of how .map() might be implemented - does it even have a reference to our App class instance to bind if it wanted to.

So when the .map() function calls _renderListItemName…well it makes its best available move and sets this as undefined. And thats why we get our error here li.addEventListener( 'click', this._logClick ); - we’re in effect trying to take the _logClick property of undefined.

The solution? Well the most straightforward one is to wrap the whole thing in an arrow function so we can make sure that dot-and-invoke sets the this parameter properly

this.names
    .map( name => this._renderListItemName(name) )
    .forEach( li => ul.appendChild(li) );

By using an arrow function, when we reference this, it looks one scope up to the render function and - assuming that was called properly - it will be bound properly to the intance of our app. Once done, that will allow things to render.

Another solution is to use the function’s .bind() function which will generate a new function based on the old one but with this nailed down (conceptually, this is a more automatic version of alternative 1)

this.names
    .map( this._renderListItemName.bind(this) )
    .forEach( li => ul.appendChild(li) );

A third approach might be, when the class is created to automatically bind its functions and then re-assign them

constructor( props ) {
  this.nodeType = "People list";
  this.names = ["George", "Bob", "Donna", "Erica"];
  this._renderListItemName = this._renderListItemName.bind(this);
}

Now you can’t call a version with the wrong binding even if you wanted to! The value of that property (which hides the inherited _renderListItemName property) is specifically set to a version of our function where this is bound up tight.

Finally, a fourth approach might be to eschew class-level functions entirely and use local arrows instead

render( el ) {
  const renderListItemName = ( name ) => {
    const li = document.createElement('li');
    li.textContent = name;
    li.addEventListener( 'click', this._logClick );
    return li
  }

  const ul = document.createElement('ul')
  this.names
      .map( renderListItemName )
      .forEach( li => ul.appendChild(li) );

  el.innerHTML = ``;
  el.appendChild(ul);
}

All of the above will work and which you use is largely a matter of preference and project-wide convention but understanding how this works and being able to diagnose and repair errors with its usage is absolutely mandatory so long as people keep using these patterns.

  • Btw, I mentioned there was another error that occurred when you actually clicked on the list items. Can you find and diagnose that?
blog comments powered by Disqus