Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?

arrow-functions, ecmascript-6, javascript

Solution

tl;dr: No! Arrow functions and function declarations / expressions are not equivalent and cannot be replaced blindly. If the function you want to replace does not use `this`, `arguments` and is not called with `new`, then yes.

As so often: it depends. Arrow functions have different behavior than function declarations / expressions, so let's have a look at the differences first:

1. Lexical `this` and `arguments`

Arrow functions don't have their own `this` or `arguments` binding. Instead, those identifiers are resolved in the lexical scope like any other variable. That means that inside an arrow function, `this` and `arguments` refer to the values of `this` and `arguments` in the environment the arrow function is defined in (i.e. "outside" the arrow function):

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject
// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

In the function expression case, `this` refers to the object that was created inside the `createObject`. In the arrow function case, `this` refers to `this` of `createObject` itself.

This makes arrow functions useful if you need to access the `this` of the current environment:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

Note that this also means that is not possible to set an arrow function's `this` with `.bind` or `.call`.

If you are not very familiar with `this`, consider reading

- MDN - this

- YDKJS - this & Object prototypes

2. Arrow functions cannot be called with `new`

ES2015 distinguishes between functions that are callable and functions that are constructable. If a function is constructable, it can be called with `new`, i.e. `new User()`. If a function is callable, it can be called without `new` (i.e. normal function call).

Functions created through function declarations / expressions are both constructable and callable. Arrow functions (and methods) are only callable. `class` constructors are only constructable.

If you are trying to call a non-callable function or to construct a non-constructable function, you will get a runtime error.

Knowing this, we can state the following.

Replaceable:

- Functions that don't use `this` or `arguments`.

- Functions that are used with `.bind(this)`

Not replaceable:

- Constructor functions

- Function / methods added to a prototype (because they usually use `this`)

- Variadic functions (if they use `arguments` (see below))

- Generator functions, which require the `function*` notation

Lets have a closer look at this using your examples:

Constructor function

This won't work because arrow functions cannot be called with `new`. Keep using a function declaration / expression or use `class`.

Prototype methods

Most likely not, because prototype methods usually use `this` to access the instance. If they don't use `this`, then you can replace it. However, if you primarily care for concise syntax, use `class` with its concise method syntax:

class User {
  constructor(name) {
    this.name = name;
  }
  
  getName() {
    return this.name;
  }
}

Object methods

Similarly for methods in an object literal. If the method wants to reference the object itself via `this`, keep using function expressions, or use the new method syntax:

const obj = {
  getName() {
    // ...
  },
};

Callbacks

It depends. You should definitely replace it if you are aliasing the outer `this` or are using `.bind(this)`:

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

But: If the code which calls the callback explicitly sets `this` to a specific value, as is often the case with event handlers, especially with jQuery, and the callback uses `this` (or `arguments`), you cannot use an arrow function!

Variadic functions

Since arrow functions don't have their own `arguments`, you cannot simply replace them with an arrow function. However, ES2015 introduces an alternative to using `arguments`: the rest parameter.

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

Related question:

- When should I use arrow functions in ECMAScript 6?

- Do ES6 arrow functions have their own arguments or not?

- What are the differences (if any) between ES6 arrow functions and functions bound with Function.prototype.bind?

- How to use arrow functions (public class fields) as class methods?

Further resources:

- MDN - Arrow functions

- YDKJS - Arrow functions

Problem

Arrow functions in ES2015 provide a more concise syntax. - Can I replace all my function declarations / expressions with arrow functions now? - What do I have to look out for? Examples: Constructor function ``` function User(name) { this.name = name; } // vs const User = name => { this.name = name; }; ``` Prototype methods ``` User.prototype.getName = function() { return this.name; }; // vs User.prototype.getName = () => this.name; ``` Object (literal) methods ``` const obj = { getName: function() { // ... } }; // vs const obj = { getName: () => { // ... } }; ``` Callbacks ``` setTimeout(function() { // ... }, 500); // vs setTimeout(() => { // ... }, 500); ``` Variadic functions ``` function sum() { let args = [].slice.call(arguments); // ... } // vs const sum = (...args) => { // ... }; ```

Original source

Related problems