620

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) => {
  // ...
};
jarmod
  • 46,751
  • 9
  • 81
  • 86
Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • 6
    Similar questions about arrow functions have come up more and more with ES2015 becoming more popular. I didn't feel like there was a good canonical question/answer for this issue so I created this one. If you think that there already is a good one, please let me know and I will close this one as duplicate or delete it. Feel free to improve the examples or add new ones. – Felix Kling Dec 18 '15 at 17:59
  • 2
    What about [JavaScript ecma6 change normal function to arrow function](http://stackoverflow.com/q/31975772/1048572)? Of course, a normal question can never be as good and generic as one specifically written to be a canonical. – Bergi Dec 18 '15 at 23:53
  • Related post - [When should I use Arrow functions in ECMAScript 6?](https://stackoverflow.com/q/22939130/465053) – RBT Jun 26 '19 at 08:36
  • [Look at this Plnkr example](http://plnkr.co/edit/09Lh6D) The variable `this` is very different `timesCalled` increments only by 1 each time the button is called. Which answers my personal question: `.click( () => { } )` and `.click(function() { })` _both create the same number of functions when used in a loop as you can see from the Guid count in the Plnkr._ – jmbmage Sep 12 '16 at 14:01

3 Answers3

866

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

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

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:

Further resources:

hilias
  • 67
  • 3
  • 9
Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • 8
    Possibly worth mentioning that the lexical `this` also affects `super` and that they have no `.prototype`. – loganfsmyth Dec 18 '15 at 22:13
  • 1
    It would also be good to mention that they aren't syntactically interchangeable -- an arrow function (`AssignmentExpression`) can't just be dropped in everywhere a function expression (`PrimaryExpression`) can and it trips people up fairly frequently (especially since there've been parsing errors in major JS implementations). – JMM Apr 01 '16 at 22:49
  • @JMM: *"it trips people up fairly frequently"* can you provide a concrete example? Skimming over the spec, it seems that the places where you can put a FE but not an AF would result in runtime errors anyway... – Felix Kling Apr 01 '16 at 22:54
  • Sure, I mean stuff like trying to immediately invoke an arrow function like a function expression (`() => {}()`) or do something like `x || () => {}`. That's what I mean: runtime (parse) errors. (And even though that's the case, fairly frequently people think the error is in error.) Are you just trying to cover logic errors that would go unnoticed because they don't necessarily error when parsed or executed? `new`'ing one is a runtime error right? – JMM Apr 01 '16 at 23:27
  • Here are some links of it coming up in the wild: [substack/node-browserify#1499](https://github.com/substack/node-browserify/issues/1499), [babel/babel-eslint#245](https://github.com/babel/babel-eslint/issues/245) (this is an async arrow, but I think it's the same basic issue), and a bunch of issues on Babel that are hard to find now, but here's one [T2847](https://phabricator.babeljs.io/T2847). – JMM Apr 01 '16 at 23:27
  • @JMM: I see what you mean. Yeah, I was more focused on behavior, less on syntax. But I can expand on that in the answer. Or feel free to edit it yourself :) *"new'ing one is a runtime error right?"* Yes. – Felix Kling Apr 01 '16 at 23:31
  • Ok cool. I think it'd be helpful to explain because I think people already find it surprising and would find it more so after reading a breakdown of how they're not interchangeable that didn't mention it. But it's true that in a non-bugged implementation it won't fly under the radar (though like I mentioned people tend to find it so surprising / are so used to a bugged implementation that they think the error is a bug.) – JMM Apr 01 '16 at 23:42
  • I'm not a fan of this answer because storing `that` outside the function and writing it with `this` is not always guaranteed to work in the case of nested objects/functions. It depends on where `that` is set to `this`. I see what you're trying to demonstrate, but there is a huge chance of misreading it. Anon functions provide some capabilities that can not be recreated with the short hand and that point should be the one you're driving home. – user157251 Jun 03 '16 at 23:25
  • @EvanCarroll: I don't mean to promote the `var that = this;` pattern. This is just included because it is a common pattern and exactly the one that arrow functions are supposed to replace. – Felix Kling Jun 04 '16 at 01:51
  • @FelixKling Great grasp of important concepts. Though I just noticed you missed the difference between function expressions and function declarations. Function declarations statements will always begin with the function keyword while function expressions begin with anything other than the function keyword. This is what makes IIFEs, a function assigned to a variable among others function expressions as opposed to function declarations. Really good job though explaining all these. Kudos. – candy_man Dec 06 '17 at 07:39
  • @Paul: thank you! There are other q&a’s with more details on function declaration vs expression. This q&a assumes that the difference is known. While I understand what you are saying, it’s not quite correct: function expressions also always start with the `function` keyword, but what comes before the `function` keyword determines whether the function definition is interpreted as declaration or expression. That part is not part of the function expression though. E.g. if you have `foo = function (){}`, then the `=` is part of the assignment expression, not the function expression. – Felix Kling Dec 09 '17 at 13:57
  • @PaulUpendo: Have a look at https://astexplorer.net if you are interested in how JavaScript programs are constructed syntactically (or at least what the AST looks like that popular JS parsers produce). – Felix Kling Dec 09 '17 at 13:59
  • @FelixKling Function Declaration Image on https://astexplorer.net/ : https://ibb.co/dKjY9b vs Function Expression Image on https://astexplorer.net/ : https://ibb.co/itp0pb – candy_man Dec 11 '17 at 06:54
  • @PaulUpendo: Right. And if you hover over the function expression in the tree view you can see that the `(...)()` part is not part of the function expression. That’s all that I’m saying. – Felix Kling Dec 11 '17 at 17:22
  • At the file level, there is no difference between `function foo(...) { return ... }` and `const foo = (...) => { return ... }`, yet I see the latter pattern used very frequently, especially in React libraries. Why create an anonymous function and assign it to a constant, when a regular function declaration would do the same thing? – Dan Dascalescu Aug 13 '18 at 22:17
  • @Dan: `¯\_(ツ)_/¯` ... people like to use new things maybe? In theory you can overwrite `foo` in the first case later, while `const` prevents that. Not sure that's the reason though. – Felix Kling Aug 13 '18 at 22:26
  • In your first example you do not overwrite anything... you just return a other object then you pass to the this-parameter of call() – Ini Sep 04 '18 at 16:07
  • @FelixKling Isn't it true that: (1) the `this` inside of an arrow function helps solve the "lost binding" in old JS, and (2) even for `foo.bar()`, there was a rule that says "the `this` inside of `bar()`'s code will be bound to `foo`" does not apply any more? See `wah = { a: 1, f: () => console.log("I am", this.a) }; wah.f()` printing `I am undefined` – nonopolarity Sep 10 '19 at 08:28
  • @JMM you can easily solve the IIFE by evaluating the function first. `(()=>{})()` this works. For The second example you can declare a function before and then pass a reference (best practice) otherwise, wrap it around parenthesis and you will be fine. `const x = undefined || (() => {})` – Michael Mammoliti Oct 22 '19 at 12:04
  • @MichaelMammoliti I'm well aware :) I was explaining that function expressions and arrow functions are not syntactically interchangeable, which was a source of confusion for some people, and why you need the parentheses. See for example the [babel-eslint issue](https://github.com/babel/babel-eslint/issues/245) I linked where I explained that the arrow function has to be wrapped in parens to be immediately invoked because it's an `AssignmentExpression` and not syntactically interchangeable with a function expression. – JMM Oct 22 '19 at 17:30
22

Arrow functions => best ES6 feature so far. They are a tremendously powerful addition to ES6, that I use constantly.

Wait, you can't use arrow function everywhere in your code, its not going to work in all cases like this where arrow functions are not usable. Without a doubt, the arrow function is a great addition it brings code simplicity.

But you can’t use an arrow function when a dynamic context is required: defining methods, create objects with constructors, get the target from this when handling events.

Arrow functions should NOT be used because:

  1. They do not have this

    It uses “lexical scoping” to figure out what the value of “this” should be. In simple word lexical scoping it uses “this” from the inside the function’s body.

  2. They do not have arguments

    Arrow functions don’t have an arguments object. But the same functionality can be achieved using rest parameters.

    let sum = (...args) => args.reduce((x, y) => x + y, 0) sum(3, 3, 1) // output - 7 `

  3. They cannot be used with new

    Arrow functions can't be construtors because they do not have a prototype property.

When to use arrow function and when not:

  1. Don't use to add function as a property in object literal because we can not access this.
  2. Function expressions are best for object methods. Arrow functions are best for callbacks or methods like map, reduce, or forEach.
  3. Use function declarations for functions you’d call by name (because they’re hoisted).
  4. Use arrow functions for callbacks (because they tend to be terser).
Community
  • 1
  • 1
Ashutosh
  • 662
  • 6
  • 19
  • 2
    the 2. They do not have arguments, I am sorry isn't true, one can have argument without use the ... operator, maybe you want to say that thy do not have array as argument – Carmine Tambascia Mar 02 '20 at 10:32
  • @CarmineTambascia Read about the special `arguments` object which is not available in arrow functions here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments – vichle Apr 16 '20 at 10:23
2

To use arrow functions with function.prototype.call, I made a helper function on the object prototype:

  // Using
  // @func = function() {use this here} or This => {use This here}
  using(func) {
    return func.call(this, this);
  }

usage

  var obj = {f:3, a:2}
  .using(This => This.f + This.a) // 5

Edit

You don't NEED a helper. You could do:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
  • 16,852
  • 9
  • 86
  • 91