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) => { // ... }; ```
Related problems
- How does the "this" keyword work, and when should it be used?
- How to use arrow functions (public class fields) as class methods?
- When should I use arrow functions in ECMAScript 6?
- What are the differences (if any) between ES6 arrow functions and functions bound with Function.prototype.bind?
- Do ES6 arrow functions have their own arguments or not?