Arrow Functions

One of the most interesting new parts of ECMAScript 6 is the arrow function. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an “arrow” (=>). But arrow functions behave differently than traditional JavaScript functions in a number of important ways:

  • No this, super, arguments, and new.target bindings - The value of this, super, arguments, and new.target inside of the function is by the closest containing nonarrow function. (super is covered in Chapter 4.)
  • Cannot be called with new - Arrow functions do not have a [[Construct]] method and therefore cannot be used as constructors. Arrow functions throw an error when used with new.
  • No prototype - since you can’t use new on an arrow function, there’s no need for a prototype. The prototype property of an arrow function doesn’t exist.
  • Can’t change this - The value of this inside of the function can’t be changed. It remains the same throughout the entire lifecycle of the function.
  • No arguments object - Since arrow functions have no arguments binding, you must rely on named and rest parameters to access function arguments.
  • No duplicate named parameters - arrow functions cannot have duplicate named parameters in strict or nonstrict mode, as opposed to nonarrow functions that cannot have duplicate named parameters only in strict mode.

There are a few reasons for these differences. First and foremost, this binding is a common source of error in JavaScript. It’s very easy to lose track of the this value inside a function, which can result in unintended program behavior, and arrow functions eliminate this confusion. Second, by limiting arrow functions to simply executing code with a single this value, JavaScript engines can more easily optimize these operations, unlike regular functions, which might be used as a constructor or otherwise modified.

The rest of the differences are also focused on reducing errors and ambiguities inside of arrow functions. By doing so, JavaScript engines are better able to optimize arrow function execution.

I> Note: Arrow functions also have a name property that follows the same rule as other functions.

Arrow Function Syntax

The syntax for arrow functions comes in many flavors depending upon what you’re trying to accomplish. All variations begin with function arguments, followed by the arrow, followed by the body of the function. Both the arguments and the body can take different forms depending on usage. For example, the following arrow function takes a single argument and simply returns it:

  1. var reflect = value => value;
  2. // effectively equivalent to:
  3. var reflect = function(value) {
  4. return value;
  5. };

When there is only one argument for an arrow function, that one argument can be used directly without any further syntax. The arrow comes next and the expression to the right of the arrow is evaluated and returned. Even though there is no explicit return statement, this arrow function will return the first argument that is passed in.

If you are passing in more than one argument, then you must include parentheses around those arguments, like this:

  1. var sum = (num1, num2) => num1 + num2;
  2. // effectively equivalent to:
  3. var sum = function(num1, num2) {
  4. return num1 + num2;
  5. };

The sum() function simply adds two arguments together and returns the result. The only difference between this arrow function and the reflect() function is that the arguments are enclosed in parentheses with a comma separating them (like traditional functions).

If there are no arguments to the function, then you must include an empty set of parentheses in the declaration, as follows:

  1. var getName = () => "Nicholas";
  2. // effectively equivalent to:
  3. var getName = function() {
  4. return "Nicholas";
  5. };

When you want to provide a more traditional function body, perhaps consisting of more than one expression, then you need to wrap the function body in braces and explicitly define a return value, as in this version of sum():

  1. var sum = (num1, num2) => {
  2. return num1 + num2;
  3. };
  4. // effectively equivalent to:
  5. var sum = function(num1, num2) {
  6. return num1 + num2;
  7. };

You can more or less treat the inside of the curly braces the same as you would in a traditional function, with the exception that arguments is not available.

If you want to create a function that does nothing, then you need to include curly braces, like this:

  1. var doNothing = () => {};
  2. // effectively equivalent to:
  3. var doNothing = function() {};

Curly braces are used to denote the function’s body, which works just fine in the cases you’ve seen so far. But an arrow function that wants to return an object literal outside of a function body must wrap the literal in parentheses. For example:

  1. var getTempItem = id => ({ id: id, name: "Temp" });
  2. // effectively equivalent to:
  3. var getTempItem = function(id) {
  4. return {
  5. id: id,
  6. name: "Temp"
  7. };
  8. };

Wrapping the object literal in parentheses signals that the braces are an object literal instead of the function body.

Creating Immediately-Invoked Function Expressions

One popular use of functions in JavaScript is creating immediately-invoked function expressions (IIFEs). IIFEs allow you to define an anonymous function and call it immediately without saving a reference. This pattern comes in handy when you want to create a scope that is shielded from the rest of a program. For example:

  1. let person = function(name) {
  2. return {
  3. getName: function() {
  4. return name;
  5. }
  6. };
  7. }("Nicholas");
  8. console.log(person.getName()); // "Nicholas"

In this code, the IIFE is used to create an object with a getName() method. The method uses the name argument as the return value, effectively making name a private member of the returned object.

You can accomplish the same thing using arrow functions, so long as you wrap the arrow function in parentheses:

  1. let person = ((name) => {
  2. return {
  3. getName: function() {
  4. return name;
  5. }
  6. };
  7. })("Nicholas");
  8. console.log(person.getName()); // "Nicholas"

Note that the parentheses are only around the arrow function definition, and not around ("Nicholas"). This is different from a formal function, where the parentheses can be placed outside of the passed-in parameters as well as just around the function definition.

No this Binding

One of the most common areas of error in JavaScript is the binding of this inside of functions. Since the value of this can change inside a single function depending on the context in which the function is called, it’s possible to mistakenly affect one object when you meant to affect another. Consider the following example:

  1. var PageHandler = {
  2. id: "123456",
  3. init: function() {
  4. document.addEventListener("click", function(event) {
  5. this.doSomething(event.type); // error
  6. }, false);
  7. },
  8. doSomething: function(type) {
  9. console.log("Handling " + type + " for " + this.id);
  10. }
  11. };

In this code, the object PageHandler is designed to handle interactions on the page. The init() method is called to set up the interactions, and that method in turn assigns an event handler to call this.doSomething(). However, this code doesn’t work exactly as intended.

The call to this.doSomething() is broken because this is a reference to the object that was the target of the event (in this case document), instead of being bound to PageHandler. If you tried to run this code, you’d get an error when the event handler fires because this.doSomething() doesn’t exist on the target document object.

You could fix this by binding the value of this to PageHandler explicitly using the bind() method on the function instead, like this:

  1. var PageHandler = {
  2. id: "123456",
  3. init: function() {
  4. document.addEventListener("click", (function(event) {
  5. this.doSomething(event.type); // no error
  6. }).bind(this), false);
  7. },
  8. doSomething: function(type) {
  9. console.log("Handling " + type + " for " + this.id);
  10. }
  11. };

Now the code works as expected, but it may look a little bit strange. By calling bind(this), you’re actually creating a new function whose this is bound to the current this, which is PageHandler. To avoid creating an extra function, a better way to fix this code is to use an arrow function.

Arrow functions have no this binding, which means the value of this inside an arrow function can only be determined by looking up the scope chain. If the arrow function is contained within a nonarrow function, this will be the same as the containing function; otherwise, this is equivalent to the value of this in the global scope. Here’s one way you could write this code using an arrow function:

  1. var PageHandler = {
  2. id: "123456",
  3. init: function() {
  4. document.addEventListener("click",
  5. event => this.doSomething(event.type), false);
  6. },
  7. doSomething: function(type) {
  8. console.log("Handling " + type + " for " + this.id);
  9. }
  10. };

The event handler in this example is an arrow function that calls this.doSomething(). The value of this is the same as it is within init(), so this version of the code works similarly to the one using bind(this). Even though the doSomething() method doesn’t return a value, it’s still the only statement executed in the function body, and so there is no need to include braces.

Arrow functions are designed to be “throwaway” functions, and so cannot be used to define new types; this is evident from the missing prototype property, which regular functions have. If you try to use the new operator with an arrow function, you’ll get an error, as in this example:

  1. var MyType = () => {},
  2. object = new MyType(); // error - you can't use arrow functions with 'new'

In this code, the call to new MyType() fails because MyType is an arrow function and therefore has no [[Construct]] behavior. Knowing that arrow functions cannot be used with new allows JavaScript engines to further optimize their behavior.

Also, since the this value is determined by the containing function in which the arrow function is defined, you cannot change the value of this using call(), apply(), or bind().

Arrow Functions and Arrays

The concise syntax for arrow functions makes them ideal for use with array processing, too. For example, if you want to sort an array using a custom comparator, you’d typically write something like this:

  1. var result = values.sort(function(a, b) {
  2. return a - b;
  3. });

That’s a lot of syntax for a very simple procedure. Compare that to the more terse arrow function version:

  1. var result = values.sort((a, b) => a - b);

The array methods that accept callback functions such as sort(), map(), and reduce() can all benefit from simpler arrow function syntax, which changes seemingly complex processes into simpler code.

No arguments Binding

Even though arrow functions don’t have their own arguments object, it’s possible for them to access the arguments object from a containing function. That arguments object is then available no matter where the arrow function is executed later on. For example:

  1. function createArrowFunctionReturningFirstArg() {
  2. return () => arguments[0];
  3. }
  4. var arrowFunction = createArrowFunctionReturningFirstArg(5);
  5. console.log(arrowFunction()); // 5

Inside createArrowFunctionReturningFirstArg(), the arguments[0] element is referenced by the created arrow function. That reference contains the first argument passed to the createArrowFunctionReturningFirstArg() function. When the arrow function is later executed, it returns 5, which was the first argument passed to createArrowFunctionReturningFirstArg(). Even though the arrow function is no longer in the scope of the function that created it, arguments remains accessible due to scope chain resolution of the arguments identifier.

Identifying Arrow Functions

Despite the different syntax, arrow functions are still functions, and are identified as such. Consider the following code:

  1. var comparator = (a, b) => a - b;
  2. console.log(typeof comparator); // "function"
  3. console.log(comparator instanceof Function); // true

The console.log() output reveals that both typeof and instanceof behave the same with arrow functions as they do with other functions.

Also like other functions, you can still use call(), apply(), and bind() on arrow functions, although the this-binding of the function will not be affected. Here are some examples:

  1. var sum = (num1, num2) => num1 + num2;
  2. console.log(sum.call(null, 1, 2)); // 3
  3. console.log(sum.apply(null, [1, 2])); // 3
  4. var boundSum = sum.bind(null, 1, 2);
  5. console.log(boundSum()); // 3

The sum() function is called using call() and apply() to pass arguments, as you’d do with any function. The bind() method is used to create boundSum(), which has its two arguments bound to 1 and 2 so that they don’t need to be passed directly.

Arrow functions are appropriate to use anywhere you’re currently using an anonymous function expression, such as with callbacks. The next section covers another major ECMAScript 6 development, but this one is all internal, and has no new syntax.