When to use it: JavaScript Arrow (aka Stabby) Functions

Get your brains ready folks, this is a mental adventure that you don’t want to miss!

Stabby functions, also known by their more boring official name Arrow Functions, were introduced to help eliminate this hacks.  What are this hacks?

I think we’ve all written code like the type below, and wondered why the interpreter puked up “Uncaught TypeError: Cannot read property ‘appendChild’ of undefined:”

function Astronauts(root, astronautObjs, nationality) {
  this.root = root;
  this.astronautObjs = astronautObjs;
  this.nationality = nationality;
}

Astronauts.prototype.show = function() {
  this.astronautObjs.forEach(function(astronaut) {
    var li = document.createElement("li");
    li.innerHTML = astronaut.name + " (" + this.nationality + ")";
    this.root.appendChild(li);
  });
}

Of course the reason is that in the forEach callback, this does not refer to the instance of Astronauts that called show().  Instead, it is the global/window object.

To work around the error, there’s a couple of idioms that JS developers use.  First, the me/_this/that hack:

function Astronauts(root, astronautObjs, nationality) {
  this.root = root;
  this.astronautObjs = astronautObjs;
  this.nationality = nationality;
}

Astronauts.prototype.show = function() {
  var me = this;
  this.astronautObjs.forEach(function(astronaut) {
    var li = document.createElement("li");
    li.innerHTML = astronaut.name + " (" + me.nationality + ")";
    me.root.appendChild(li);
  });
}

The name that JS developers use varies, but I like me because it’s short.  In this idiom, the value of this from the enclosing context is passed to the forEach callback by assigning it to me, which is closed over by the anonymous function passed to the forEach.  Now the callback can use properties from the this that it expects.

The second idiom is the bind(this) hack:

Astronauts.prototype.show = function() {
  var appendName = function(astronaut) {
    var li = document.createElement("li");
    li.innerHTML = astronaut.name + " (" + this.nationality + ")";
    this.root.appendChild(li);
  }.bind(this);

  this.names.forEach(appendName);
}

Here, the value of this from the enclosing context is preserved by passing the enclosing context’s this value to bind(), which sets the function’s context to the enclosing context’s this value.

Reasonable people may disagree on whether these are hacks, or that a language feature is needed to eliminate these idioms.  Personally, I’ve encountered this issue more than I’d care to admit and am glad to see it addressed in ES2015.

Down the rabbit hole

If you are relatively new to JS like myself, right now you may be thinking “WTF is going on?”  What is this?  When does the value change?  How does it just appear in functions?  What is bind doing?

Since this post is about arrow functions, which have different rules for this when compared to regular functions, I’ll just cover the basics that are needed to understand what is different in arrow functions.  To fill in the gaps, I recommend going through the Udacity Object Oriented Javascript course.

So basically, this in a regular function contains the value of the object that invoked the function.  A decent rule of thumb is that you can find what the value of this will be by looking at the code that called the function.  Inside the callee function, this will have the value of the object to the left of the dot at run time in the caller function.

function Astronauts(root, astronautObjs, nationality) {
  this.root = root;
  this.astronautObjs = astronautObjs;
  this.nationality = nationality;
}

Astronauts.prototype.show = function() {
  console.log(this);
  var me = this;
  this.astronautObjs.forEach(function(astronaut) {
    var li = document.createElement("li");
    li.innerHTML = astronaut.name + " (" + me.nationality + ")";
    me.root.appendChild(li);
  });
}

var ul = document.createElement("ul");
var astronautsUS = new Astronauts(ul, [{name: "Alan Shepard"}, {name: "Gus Grissom"}], "American");

// show() will log astronautsUS
astronautsUS.show();

Note that this is always determined at run time, never at write time.

// show() will log the window/global object, because when the function is invoked
// without an explicit caller, the object that invokes the function is the
// window/global object. Also throws an "Uncaught TypeError" when execution
// reaches the forEach, because the window/global object doesn't have an
// astronautObjs property.
var show = astronautsUS.show;
show();

bind can be used to pin a this value, overriding the default behavior:

// just like calling astronautsUS.show()
// showBind() will log astronautsUS
var showBind = show.bind(astronautsUS);
showBind();

Back to Arrow Functions

Here’s that code written using an arrow function in the callback instead of a regular function.  This shows the main differences between the two types of functions.

Astronauts.prototype.show = function() {
  console.log(this);
  this.astronautObjs.forEach(astronaut => {
    var li = document.createElement("li");
    li.innerHTML = astronaut.name + " (" + this.nationality + ")";
    this.root.appendChild(li);
  });
}

The first difference is major.  In the callback, this no longer refers to the (window/global) object that invoked the function, i.e. it no longer refers to the function’s context.  Instead, this is lexically scoped, meaning that the value of this in the callback is determined by the value of this in the enclosing scope.  Therefore, the callback no longer requires the use of var me = this; to use astronautsUS.nationality and astronautsUS.root.  Instead, it simply uses this.

The second difference, which is minor in my opinion, is that the syntax is more terse.  The function keyword has been eliminated, as has the parentheses around the single parameter.  The terseness really is apparent when writing single-line functions, though again reasonable people may disagree on which is more readable:

// these two functions do the same thing
var mulBy2 = function(a) {
  return a * 2;
}
var mulBy2Arrow = a => a * 2;

When To Use It

There’s quite a bit more to arrow functions than are detailed here, including the syntax for zero or more than one parameter, the single-line syntax for returning objects, how Function.prototype.bind and Function.prototype.apply are different when used with arrow functions vs. regular functions, using rest parameter syntax rather than the implicit arguments parameter, and that arrow functions are required to be anonymous.  However, in my opinion, none of these details matter for determining when to use arrow functions.

Arrow functions are designed to significantly reduce the use of this hacks, hopefully making code shorter, more readable, and less error-prone.  We’ve already seen an example of erroneous code that’s commonly written by accident or due to poor understanding of this.

The terseness can also be an asset, especially true when dealing with a lot of one-line callbacks.  Again, the readability of the terse syntax is debatable but IMO it’s generally more readable in situations like the one below:

var a = functionThatReturnsLongArray();

// this gets shortened:
var reducedArray = a.filter(function(item) {
 return item.property > THRESHOLD;
}).reduce(function(acc, cur) {
  return acc += cur.property;
}, 0);

// to this:
var reducedArrayArrow = a
  .filter(item => item.property > THRESHOLD)
  .reduce(((acc, cur) => acc += cur.property), 0);

Therefore, my recommendation is to use arrow functions in callbacks.

I’m also specifically going to specifically discourage using arrow functions for prototype method declarations:

function Astronauts(root, astronautObjs, nationality) {
  this.root = root;
  this.astronautObjs = astronautObjs;
  this.nationality = nationality;
}

// do this:
Astronauts.prototype.show = function() { ... }

// NOT THIS:
Astronauts.prototype.DoNotDoThis = () => { ... }

The reason is that typically, when calling a function on an object’s prototype, you want the value of this to refer to the object that called the function so that it can use the properties associated with the calling object.  If you use an arrow function in this case, the function will have no useful value for this and it will be more error prone to use the values associated with the calling object.  If you want a function that doesn’t have a useful value for this, you’ll write less code, and the code will be less error-prone, by simply writing a function in the global scope.

Please let me know your thoughts on Arrow Functions by leaving a comment below!

Leave a Reply

Your email address will not be published. Required fields are marked *