Skip to content

Instantly share code, notes, and snippets.

@swyxio
Last active September 5, 2017 20:30
Show Gist options
  • Save swyxio/5daf6ff464430e1094144b70cb82a851 to your computer and use it in GitHub Desktop.
Save swyxio/5daf6ff464430e1094144b70cb82a851 to your computer and use it in GitHub Desktop.
the precedence order of `this`

Precedence

In many card games, there are certain cards or combinations of cards that "beat" or "trump" others. For example, in poker, a straight beats a three of a kind, and a three of a kind beats a two of a kind. Likewise, in the case of ties between two similar hands, an Ace will beat a King, and so on and so forth.

Precedence in programming languages is just like this. In regards to this context, we know quite a couple different ways that this context can be set - first there's the normal way (set dynamically as method on an object), then there's the new keyword, then also call, apply and bind, and now arrow functions. But what happens if these different methods collide? For example:

fig 1

const Dog = (name, breed) => {
  this.name = name;
  this.breed = breed;
}

const cody = new Dog('Cody', 'pug');

Does the lexical this set by the arrow function cause this to refer to the global context? Or does the new keyword set it to be a brand new object that the Dog function will return?

Another example (without arrow functions at all):

fig 2

const Cody = { name: 'Cody' };
const Lexie = { name: 'Lexie' };

const sayName = function () {
  console.log(this.name);
}

const boundSayHello = sayName.bind(Lexie);
Cody.sayName = boundSayHello;
Cody.sayName();

Whose name will come out? Cody's (because sayName is invoked as a method on the Cody object), or Lexie's (because Cody.sayName refers to a function that is bound to the Lexie object)?

There are many situations like this, but fortunately figuring them out is as simple as learning the rules to a card game. Whenever two or more different ways of setting this context "fight", the winner is resolved in the following order:

  1. If the function is an arrow function, then lexical this context wins. Hands down, no exceptions.
  2. If the function uses the new keyword, then this refers to the new object that gets constructed.
  3. If a function is bound using bind, then this will refer to the bound object. Note that functions cannot be "re-bound" - if you create a bound function, and then try to bind the bound function to another new context, the original context will win.
  4. If the function sets this context explicitly using call or apply, then the object that was explicitly specified by one of those methods wins.
  5. If the function is invoked as a method on an object (the object in this case may also be referred to as the call-site of the function), then the object will be this.
  6. If none of the above apply, then this will be refer to the global context (ex. window in the browser), or if you're using 'use strict', then it will be undefined.

To re-iterate, the order above matters. That is, arrow functions will beat the new keyword, which beats bind, which beats call and apply, which beats dynamic call-site, which beats nothing at all.

EXERCISE

Using these rules, what do you think this refers to in the two code examples above (fig 1 and fig 2)?

SOLUTION

In fig 1, the answer is the global context set by the arrow function! Arrow functions are even more powerful than the `new` keyword! This means you should never use an arrow function as a constructor function.

In fig 2, the answer is "Lexie". While the dynamic call site of the function is Cody, the function that Cody invokes is bound to Lexie, and `bind` is higher on our list, so the context set by `bind` wins.

FURTHER READING

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment