ES6 arrow functions, syntax and lexical scoping

ES2015 (ES6) introduces a really nice feature that punches above its weight in terms of simplicity to integrate versus time saving and feature output. This feature is the arrow function.
Before we dive into the features of the arrow function and what it actually does for us, let’s understand what an arrow function is not. It’s not a replacement for the function keyword, at all. This means you can’t do a find and replace on every single function keyword and everything works perfectly, because it likely won’t.
If you’re competent with the way JavaScript scope works, and have a great understanding of lexical scope, the this keyword and Prototype methods such as .call().apply() and .bind(), then you’re in good hands to continue reading.

Syntax

Let’s look at what the arrow function’s construct is from MDN:
// example 1
([param] [, param]) => {
  statements
}

// example 2
param => expression
The “normal JavaScript” (ES5) equivalents to help transition:
// example 1
function ([param] [, param]) {
  statements
}

// example 2
function (param) {
  return expression
}
The ES6 and ES5 differences in example 1 are that the function keyword is omitted, and => now exists after the arguments. In example 2, our function has been reduced to one line, this is great for single line function expressions that get return‘d.

Hint: arrows are anonymous

Arrow functions are always anonymous, which means we can’t do this with ES6:
// ES5
function doSomething() {
  //...
}
Instead of this, we could assign our anonymous arrow function it to a variable (using var here instead of let as ES6 block scoping is another topic):
// ES6
var doSomething = () => {
  //...
}
Let’s look at the syntaxes a little further and then the functionality differences when using arrow functions.

Syntax: single line expressions

We touched briefly above on single line expressions, let’s look at a great use case for them.
Let’s take some junky ES5 example that iterates over an Array using Array.prototype.map:
var numbers = [1,2,3,4,5];
var timesTwo = numbers.map(function (number) {
  return number * 2;
});
console.log(timesTwo); // [2, 4, 6, 8, 10]
We can reduce this down to a single line with an arrow function, which saves us a lot of typing and can actually enhance readability in my opinion as this piece of code has one clear role:
var numbers = [1,2,3,4,5];
var timesTwo = numbers.map((number) => number * 2);
console.log(timesTwo); // [2, 4, 6, 8, 10]

Syntax: single argument functions

Arrow functions also give us a small “sugar” syntax that allows us to remove parenthesis when only using a single argument in a function.
Taking the last piece of code for example we had this:
numbers.map((number) => number * 2);
When we could remove the parens from (number) to leave us with this:
numbers.map(number => number * 2);
This is great and a little clearer initially, but as we all know applications grow and code scales, and to save us headaches (be it forgetting syntaxes or lesser experienced developers “not knowing” to add parens back with more than one argument), I’d recommend always using the parens out of habit, even for single args:
// we still rock with ES6
numbers.map((number) => number * 2);

Functionality: lexical scoping “this”

Now we’re past the sugar syntax excitement, we can dig into the benefits of the arrow function and its implications on execution context.
Typically if we’re writing ES5, we’ll use something like Function.prototype.bind to grab the this value from another scope to change a function’s execution context. This will mainly be used in callbacks inside a different scope.
In Angular, I adopt the controllerAs syntax which allows me to use this inside the Controller to refer to itself (so here’s an example). Inside a function the this value may change, so I could have a few options, use that = this or .bind:
function FooCtrl (FooService) {
  this.foo = 'Hello';
  FooService
  .doSomething(function (response) {
    this.foo = response;
  });
}
The this.foo = response; won’t work correctly as it’s been executed in a different context. To change this we could use .bind(this) to give our desired effect:
function FooCtrl (FooService) {
  this.foo = 'Hello';
  FooService
  .doSomething(function (response) {
    this.foo = response;
  }.bind(this));
}
Or you may be used to keeping a top level this reference, which can make more sense when dealing with many nested contexts, we don’t want a gross tree of .bind(this), .bind(this), .bind(this) and a tonne of wasted time binding those new functions (.bind is very slow). So we could look at that = this to save the day:
function FooCtrl (FooService) {
  var that = this;
  that.foo = 'Hello';
  FooService
  .doSomething(function (response) {
    that.foo = response;
  });
}
With arrow functions, we have a better option, which allows us to “inherit” the scope we’re in if needed. Which means if we changed our initial example to the following, the this value would be bound correctly:
function FooCtrl (FooService) {
  this.foo = 'Hello';
  FooService
  .doSomething((response) => { // woo, pretty
    this.foo = response;
  });
}
We could then refactor some more into a nice single line expression, push to git and head home for the day:
function FooCtrl (FooService) {
  this.foo = 'Hello';
  FooService
  .doSomething((response) => this.foo = response);
}
The interesting thing to note is that the this value (internally) is not actually bound to the arrow function. Normal functions in JavaScript bind their own this value, however the this value used in arrow functions is actually fetched lexically from the scope it sits inside. It has no this, so when you use this you’re talking to the outer scope.

Comments

Popular posts from this blog

How to Choose a Technology Stack for Web Application Development

[Advance Java] Difference Between Apache and Tomcat

Setting ESLint on a React Typescript project