Felix Kling Felix Kling - 2 months ago 14
Javascript Question

Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?


Canonical question If you find a question about issues after replacing a function declaration / expression with an arrow function, please close it as duplicate of this one.


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(arguments);
// ...
}

// vs
const sum = () => {
let args = [].slice(arguments);
// ...
};

Answer

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 lets 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. This makes arrow functions useful if you need to access the this of the current environment:

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

// 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

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 througn 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))

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 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, 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(arguments);
  // ...
}

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

Related question:

Further resources:

Comments