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


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





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






What about JavaScript ecma6 change normal function to arrow function? 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




2 Answers
2



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.


this


arguments


new



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


this


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


this


arguments


this


arguments


this


arguments




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


createObject


this


this


createObject



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


this


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


this


.bind


.call



If you are not very familiar with this, consider reading


this



2. Arrow functions cannot be called with new


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


new


new User()


new



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


class



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:


this


arguments


.bind(this)



Not replaceable:


this


arguments



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.


new


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:


this


this


class


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:


this


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



Callbacks



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


this


.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!


this


this


arguments



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.


arguments


arguments


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

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



Related question:



Further resources:





might be helpful to mention up top that if the function doesn't use this or arguments inside itself, then yes, they are equivalent. that seems lost in the middle...
– dandavis
Dec 18 '15 at 18:30



this


arguments





Possibly worth mentioning that the lexical this also affects super and that they have no .prototype.
– loganfsmyth
Dec 18 '15 at 22:13


this


super


.prototype





We've got some good resources on arrow functions already, maybe you want to link them as well: Do ES6 arrow functions have their own arguments or not? [duplicate], What are the differences (if any) between ES6 arrow functions and functions bound with Function.prototype.bind? and Can i use ES6 fat arrow in class methods?
– Bergi
Dec 18 '15 at 23:51





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 1 '16 at 22:49


AssignmentExpression


PrimaryExpression





@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 1 '16 at 22:54



Look at this Plnkr example



The variable this is very different timesCalled increments only by 1 each time the button is called. Which answers my personal question:


this


timesCalled



.click( () => { } )


.click( () => { } )



and



.click(function() { })


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






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Export result set on Dbeaver to CSV

Opening a url is failing in Swift