;

Arrow This | getiblog

One of the most vaunted features of ES6 is the new => arrow function syntax for abbreviated definitions of function expressions (aka “lambdas”). You hardly can’t find a blog post or conference talk or book about ES6 (or even tangentially related to it) that doesn’t lead with “=> is the new function“.

Arrow function syntax has even permeated standards and specification documents, almost as if it’s always been there and we’re just discovering it.

Those who follow me know I’m not a fan of => syntax, for a number of reasons. But don’t worry, this blog post is not about why I don’t like it. If you are interested in that discussion, see Chapter 2, ‘Arrow Functions’ of my YDKJS: ES6 & Beyond book.

Here I want to clear up a little confusion around what exactly arrow functions do with this, arguments, etc. Actually, I have been guilty of not precisely explaining this topic in the past, and I want to clear the record. For example, here’s how I first explained it in YDKJS awhile back.

Lexical Or Not?

So here’s how I, and a lot of other people, describe arrow function this behavior: lexical this.

What do we mean by that?

function foo() {
   setTimeout( () => {
      console.log("id:", this.id);
   },100);
}

foo.call( { id: 42 } );
// id: 42

The => arrow function here appears to bind its this to the parent function foo()‘s this. If that inner function had been a normal function (declaration or expression), its this would have been controlled by how setTimeout(..) chose to invoke the function. If you’re fuzzy on the rules that decide this-binding, check out Chapter 2, ‘Determining this in my YDKJS: this & Object Prototypes book.

Lexical Variable this

One common way of illustrating that observable this behavior is:

function foo() {
   var self = this;
   setTimeout(function() {
      console.log("id:", self.id);
   },100);
}

foo.call( { id: 42 } );
// id: 42

Side note: The variable name self is an absolutely terrible, misleading name. It implies that this refers to the function itself. It almost never does. var that = this is similarly unhelpful in its semantics, especially when there are multiple scopes in play (that1, that2, …). If you want a good name, use var context = this, because that’s what this really is: a dynamic context.

In that snippet, you can see that we don’t even use this inside the inner function. Instead, we fall back on a more predictable mechanism: lexical variables. We declare a variable self in the outer function, and then simply reference that variable in the inner function.

That of course completely eliminates this-binding rules from the equation (for the inner function, that is), and instead just relies on lexical scoping rules, and indeed, closure.

The end result appears to be the same as with => arrow functions. In other words, the (imprecise) implication here is that => arrow functions have a “lexical this” behavior by way of the lexical variable/closure mechanism.

But… that’s not accurate.

Uh oh. My bad.

Arrow-bound this

Another way of illustrating the observed this behavior of => arrow functions is to think of the inner function as being hard-bound:

function foo() {
   setTimeout(function() {
      console.log("id:", this.id);
   }.bind(this),100);
}

foo.call( { id: 42 } );
// id: 42

As you can see with .bind(this), the inner function here is hard-bound to the this of the outer function, which means that no matter how setTimeout(..) chooses to invoke the function its given, that function will use the this that foo() uses.

Yes, this version has the same observable behavior as the previous two snippets demonstrate. So, is it more accurate? Many people assume that’s really how => arrow functions work.

Eh… nope.

Oops.

Already Lexical

I’m more embarrassed by my imprecise explanations, and tolerance of others’ explanations thereof, in the past because awhile back, TC39 regular Dave Herman (@littlecalculist) had explained this issue more carefully and accurately to me, and yet I’m guilty of not fully internalizing the meaning of his explanation.

Dave said to me, essentially, “The ‘lexical this‘ phrase is troubling, because this has always been lexical.”

Really? Hmm…

He went on to say, “What => changes is not to make this lexical, but rather to not bind a this in it at all.

I didn’t catch back then the fullness of what he was saying. But I get it now.

Normal functions declare their own this, using one of those aforementioned rules. => arrow functions don’t have a this at all.

Wait… How is that possible? I can certainly use a this inside a => arrow function. Of course you can. How? Since the => arrow function doesn’t have its own this, when you use this, normal lexical scoping rules are in play, and the reference is resolved to the nearest outer scope’s definition of this.

Consider:

function foo() {
   return () => {
      return () => {
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}

foo.call( { id: 42 } )()()();
// id: 42

In that snippet, how many this-bindings do you think there are? Most would probably assume four, one for each function.

More accurately, there’s just one, in the foo() function.

The successively nested => functions don’t declare their own this, so that this.id reference simply resolves up the scope chain until it gets to foo(), the first place it finds an affirmative binding for a this.

That’s exactly the same way that any other normal lexical variable would be treated!

In other words, just as Dave said, this was already lexical, and always is lexical. => doesn’t bind a this variable so the scope lookup just continues as it always normally would.

Not Just this

The stakes at risk if you inaccurately explain the => behavior for this are that you end up thinking things like, “arrow functions are just syntax sugar for function…” They clearly are not. Nor are they sugar for var self = this or .bind(this).

Those inaccurate explanations are a classic example of getting the right answer for the wrong reason. Like back in Algebra class in high school, when you got the correct answer on a test but the teacher marked off points because you got the answer using incorrect techniques. How you get the answer matters!

Moreover, the accurate explanation — that => doesn’t bind its own this, allowing lexical scope resolution to take over — also explains another important fact of => arrow functions: they don’t just change how this works in inner functions.

In fact, => arrow functions do not bind a this, arguments, super (ES6), or new.target (ES6).

That’s right, in all four of those cases (plus probably more added in the future!), the => arrow functions don’t bind those variables locally, so any reference to them will lexically resolve up the scope chain to an outer scope.

Consider:

function foo() {
   setTimeout( () => {
      console.log("args:", arguments);
   },100);
}

foo( 2, 4, 6, 8 );
// args: [2, 4, 6, 8]

Do you see?

In that snippet, arguments is not bound by =>, so it resolves instead to the arguments of foo(..). The same would be true of super and new.target.

Why this Matters?

Update: in comments and social media, many were asking why it really matters, since the this behaves the same way regardless of the theory or implementation.

Did you know that => arrow functions cannot have their this binding hard-bound with bind(..)?

Consider:

function foo() {
   return () => {
      console.log("id:",this.id);
   };
}

var arrowfn = foo.call( { id: 42 } );

setTimeout( arrowfn.bind( { id: 100 } ), 100);
// id: 42

So why doesn’t the .bind({..}) work to produce id: 100 output?

If you have an incorrect understanding of => arrow’s this, you’ll have to invent explanations like, “the this is immutable.” Wrong.

The simple, correct answer is that since => has no this, of course .bind(obj) has nothing to operate on! Similarly, => arrows can’t be called with new. Since there’s no this, the new doesn’t have anything to bind.

Understanding how => actually treats this matters, because it’s the only way to correctly explain other observable behaviors of =>. Staying in the dark and then fumbling around for hacky explanations when you’re surprised — that’s a recipe for immature JS coding.

Wrapping Up

Don’t settle for the convenient but inaccurate answer. Don’t settle for getting the right answer the wrong way.

It matters how these things work. It matters what mental model you use, because that’s the mental model you’ll use to analyze and describe and debug other behaviors. If you’re off track in the beginning, you’ll only get further off track later.

I wish I had listened more closely to Dave back then. I wish I hadn’t inaccurately explained => behaviors for this. I take it seriously to be correct about what I think, write, and teach about JS. I’ll be more careful in the future.