Confusing JavaScript Syntax - Arrow Functions

JavaScript has been undergoing numerous changes over the past year or so. The latest version of JavaScript, ES2015, introduced many features implemented with a new and possibly confusing syntax. Some new confusing syntax additions to the JavaScript language include arrow functions, destructuring, rest parameters, spread operators, as well as, the generator functions.

Some of these new language features are available in JavaScript engines today while some require a transpiler such as Babel. This port will be the first in a series of posts on the new syntaxes, to help those new to JavaScript, and even experienced JavaScript developers to be better accustomed to the new syntax in the latest JavaScript versions.

One of the most useful, and commonly used new syntaxes is the arrow function. The post will explore the new syntax, common usages and behavior changes to JavaScript because of arrow functions.

Arrow Functions

Arrow functions reduce the code needed to create functions, and they clarify the value of this inside the function.

// no parameters
// fat arrow links parameters to the function
// returns value of expression
const now = () => Date.now();

// one parameter, no parenthesis
const square = a => a * a;

// two parameters, include parenthesis
const add = (a, b) => a + b;

// can be used with code blocks as well
// returns the value returned from the code block
const append = (s, a) => {  
  s = s + a;
  return s;
};

From the JavaScript code snippet above, the now function is the simplest form of an arrow function. Observe the fat arrow operator “=>” which is used to link the parameters to the function body. The function body can be a one-line expression, or it can be a multi-line code block. When there are no parameters, an empty set of parenthesis are used on the left-side of the fat arrow operator.

When an arrow function has one parameter, the parenthesis can be dropped, and the single parameters are placed on the left-side of the fat arrow. When multiple parameters are specified, the parameter list needed to be wrapped in parenthesis once again.

When more than a one-line expression is needed, a code block wrapped in curly braces can be used. The return value of the code block should be specified using a return keyword as is used with normal functions. A return statement is not required, and if omitted the return value will be undefined.

Also, unlike traditional function definitions, arrow functions, both with and without code blocks, must be terminated with a semicolon, unless included as part of a larger expression. Concerning syntax, they are similar to function expressions.

// function declaration, no semicolon
function doIt(x) {  
  return x * 2;
}

// function expression, ends with semicolon
const doIt = function(x) {  
  return x * 2;
};

// arrow function, expression ended with semicolon
const doIt = x => x * 2;


// arrow function, code block ended with semicolon
const doIt = x => {  
  return x * 2;
};

Usage

Typically, arrow functions are defined by assigning the function to a constant variable, or they are arguments passed into other function calls.

const append = (s, a) => {  
  s = s + a;
  return s;
};

doIt(a => a * a);  

Assigning an arrow function to a constant variable is not required. The var and let variable declarations can be used, but generally, it’s easier to maintain JavaScript applications with fewer mutable variables, and more immutable variables. Arrow functions are a good candidate for adding more immutable variables to an application.

Value of “this”

In JavaScript, a function defines a new lexical scope (scope based upon source code block structure), and a new variable scope (a scope in memory) when invoked. Lexical scope is used to facilitate the use of closures by function implementations to access variables from the surrounding lexical scope. Closures allow code such as follows:

// defined in an outer lexical scope
var t = "data";

// defines a new lexical scope in the source code
function doIt() {

    // 1. t is not declared in this lexical scope
    // 2. but t is available through a closure reference
    // 3. this "t" is a reference to the "t" in the outer scope, not to the value of "t"
    console.log(t);
}

// invoking the function produces a new variable memory scope
doIt();

Closures work for all variables except for arguments and this. The values of these variables do not pass through with closure; instead, they always represent the values of the context as determined by the invocation of the function. Because of this quality of functions, developers are often confused by what the value of this is. In languages such as Java or C#, the value of this is always determined by lexical scope within a class, whereas with JavaScript it's determined at runtime primarily determined by the invocation of the function.

This problem becomes especially confusing when nesting functions inside of functions and doing common operations such as registering event handlers. Consider the following code below:

let appForm = {  
  configure: function() {

  // value of "this" is the appForm
  console.log(this);

  document
    .querySelector('button')
    .addEventListener('click', function() {

      // value of "this" is the button, not the appForm
      console.log(this);

    });
  }
};

appForm.configure();  

Many developers, even experienced ones, forget what the value of this is in the event handler. When the application runs, the value of this will not be the value of this outside of the function, as many developers would expect. Arrow functions help to resolve this problem. Arrow functions allow the outer context of this to pass through into the function. When this is referenced, it will point to the same object as it does in the surrounding lexical scope of the arrow function.

The following code clarifies the original intent of using the object pointed to by this.

let appForm = {  
  configure: function() {

    // value of "this" is the appForm
    console.log(this);

    document
      .querySelector('button')
      .addEventListener('click', e => {

        // value of "this" is the appForm
        console.log(this);

      });
  }
};

appForm.configure();

Using the arrow function to register the event handler will set the value of this inside the event handler to the appForm object.

Conclusion

Arrow functions are a terser syntax for creating functions. When combined with const (which uses block scope, does not hoist, and declares immutable variables), arrow functions make JavaScript programs easier to reason about, and simpler to code and maintain. Also, their quality of using the surrounding of lexical scope of this clarifies code as well.

JavaScript is a constantly evolving language, gaining lots of new features which require expanding the syntax of the language to accommodate these new features. Learning these features will be critical to building new applications using the latest libraries and frameworks, and utilizing best practices.