The question is directed at people who have thought about code style in the context of the upcoming ECMAScript 6 (Harmony) and who have already worked with the language.
Arrow functions - most widely used ES6 feature so far ...
Usage : All ES5 functions should be replaced with ES6 arrow functions except in following scenarios:
Arrow functions should NOT be used:
this
/arguments
in a function
this
/arguments
of their own, they depend upon their outer context.constructor
this
.this
(which should be object itself).Let us understand some of the variants of arrow functions to understand better:
Variant 1: When we want to pass more than one argument to a function and return some value from it.
ES5 version:
var multiply = function (a,b) {
return a*b;
};
console.log(multiply(5,6)); //30
ES6 version:
var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30
Note:
function
keyword is NOT required.
=>
is required.
{}
are optional, when we do not provide {}
return
is implicitly added by JavaScript and when we do provide {}
we need to add return
if we need it.
Variant 2: When we want to pass ONLY one argument to a function and return some value from it.
ES5 version:
var double = function(a) {
return a*2;
};
console.log(double(2)); //4
ES6 version:
var doubleArrow = a => a*2;
console.log(doubleArrow(2)); //4
Note:
When passing only one argument we can omit parenthesis ()
.
Variant 3: When we do NOT want to pass any argument to a function and do NOT want to return any value.
ES5 version:
var sayHello = function() {
console.log("Hello");
};
sayHello(); //Hello
ES6 version:
var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow
Variant 4: When we want to explicitly return from arrow functions.
ES6 version:
var increment = x => {
return x + 1;
};
console.log(increment(1)); //2
Variant 5: When we want to return an object from arrow functions.
ES6 version:
var returnObject = () => ({a:5});
console.log(returnObject());
Note:
We need to wrap the object in parenthesis ()
otherwise JavaScript cannot differentiate between a block and an object.
Variant 6: Arrow functions do NOT have arguments
(an array like object) of their own they depend upon outer context for arguments
.
ES6 version:
function foo() {
var abc = i => arguments[0];
console.log(abc(1));
};
foo(2); // 2
Note:
foo
is an ES5 function, with an arguments
array like object and an argument passed to it is 2
so arguments[0]
for foo
is 2.
abc
is an ES6 arrow function since it does NOT have it's own arguments
hence it prints arguments[0]
of foo
it's outer context instead.
Variant 7: Arrow functions do NOT have this
of their own they depend upon outer context for this
ES5 version:
var obj5 = {
greet: "Hi, Welcome ",
greetUser : function(user) {
setTimeout(function(){
console.log(this.greet + ": " + user); // "this" here is undefined.
});
}
};
obj5.greetUser("Katty"); //undefined: Katty
Note:
The callback passed to setTimeout is an ES5 function and it has it's own this
which is undefined in use-strict
environment hence we get output:
undefined: Katty
ES6 version:
var obj6 = {
greet: "Hi, Welcome ",
greetUser : function(user) {
setTimeout(() => console.log(this.greet + ": " + user));
// this here refers to outer context
}
};
obj6.greetUser("Katty"); //Hi, Welcome: Katty
Note:
The callback passed to setTimeout
is an ES6 arrow function and it does NOT have it's own this
so it takes it from it's outer context that is greetUser
which has this
that is obj6
hence we get output:
Hi, Welcome: Katty
Miscellaneous:
We cannot use new
with arrow functions.
Arrow functions do Not have prototype
property.
We do NOT have binding of this
when arrow function is invoked through apply
or call
.
A while ago our team migrated all its code (a mid-sized AngularJS app) to JavaScript compiled using Traceur Babel. I'm now using the following rule of thumb for functions in ES6 and beyond:
function
in the global scope and for Object.prototype
properties.class
for object constructors.=>
everywhere else.Why use arrow functions almost everywhere?
thisObject
as the root. If even a single standard function callback is mixed in with a bunch of arrow functions there's a chance the scope will become messed up.function
immediately sticks out for defining the scope. A developer can always look up the next-higher function
statement to see what the thisObject
is.Why always use regular functions on the global scope or module scope?
thisObject
.window
object (global scope) is best addressed explicitly.Object.prototype
definitions live in the global scope (think String.prototype.truncate
etc.) and those generally have to be of type function
anyway. Consistently using function
on the global scope helps avoid errors.function foo(){}
than const foo = () => {}
— in particular outside other function calls. (2) The function name shows in stack traces. While it would be tedious to name every internal callback, naming all the public functions is probably a good idea.
Object constructors
Attempting to instantiate an arrow function throws an exception:
var x = () => {};
new x(); // TypeError: x is not a constructor
One key advantage of functions over arrow functions is therefore that functions double as object constructors:
function Person(name) {
this.name = name;
}
However, the functionally identical2 ES Harmony draft class definition is almost as compact:
class Person {
constructor(name) {
this.name = name;
}
}
I expect that use of the former notation will eventually be discouraged. The object constructor notation may still be used by some for simple anonymous object factories where objects are programmatically generated, but not for much else.
Where an object constructor is needed one should consider converting the function to a class
as shown above. The syntax works with anonymous functions/classes as well.
Readability of arrow functions
The probably best argument for sticking to regular functions - scope safety be damned - would be that arrow functions are less readable than regular functions. If your code is not functional in the first place, then arrow functions may not seem necessary, and when arrow functions are not used consistently they look ugly.
ECMAScript has changed quite a bit since ECMAScript 5.1 gave us the functional Array.forEach
, Array.map
and all of these functional programming features that have us use functions where for-loops would have been used before. Asynchronous JavaScript has taken off quite a bit. ES6 will also ship a Promise object, which means even more anonymous functions. There is no going back for functional programming. In functional JavaScript, arrow functions are preferable over regular functions.
Take for instance this (particularly confusing) piece of code3:
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(articles => Promise.all(articles.map(article => article.comments.getList())))
.then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
.then(comments => {
this.comments = comments;
})
}
The same piece of code with regular functions:
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(function (articles) {
return Promise.all(articles.map(function (article) {
return article.comments.getList();
}));
})
.then(function (commentLists) {
return commentLists.reduce(function (a, b) {
return a.concat(b);
});
})
.then(function (comments) {
this.comments = comments;
}.bind(this));
}
While any one of the arrow functions can be replaced by a standard function, there would be very little to gain from doing so. Which version is more readable? I would say the first one.
I think the question whether to use arrow functions or regular functions will become less relevant over time. Most functions will either become class methods, which make away with the function
keyword, or they will become classes. Functions will remain in use for patching classes through the Object.prototype
. In the mean time I suggest reserving the function
keyword for anything that should really be a class method or a class.
Notes
extend
keyword. A minor difference is that class declarations are constants, whereas function declarations are not.I still stand by everything I wrote in my first answer in this thread. However, my opinion on code style has developed since then, so I have a new answer to this question that builds on my last one.
Regarding lexical this
In my last answer, I deliberately eschewed an underlying belief I hold about this language, as it was not directly related to the argument I was making. Nonetheless, without this being explicitly stated, I can understand why many people simply balk at my recommendation to not use arrows, when they find arrows so useful.
My belief is this: we shouldn’t be using this
in the first place. Therefore, if a person deliberately avoids using this
in his code, then the “lexical this
” feature of arrows is of little to no value. Also, under the premise that this
is a bad thing, arrow’s treatment of this
is less of a “good thing;” instead, it’s more of a form of damage control for another bad language feature.
I figure that this either does not occur to some people, but even to those to whom it does, they must invariably find themselves working within codebases where this
appears a hundred times per file, and a little (or a lot) of damage control is all a reasonable person could hope for. So arrows can be good, in a way, when they make a bad situation better.
Even if it is easier to write code with this
with arrows than without them, the rules for using arrows remain very complex (see: current thread). Thus, guidelines are neither “clear” nor “consistent,” as you’ve requested. Even if programmers know about arrows’ ambiguities, I think they shrug and accept them anyway, because the value of lexical this
overshadows them.
All this is a preface to the following realization: if one does not use this
, then the ambiguity about this
that arrows normally cause becomes irrelevant. Arrows become more neutral in this context.
Regarding terse syntax
When I wrote my first answer, I was of the opinion that even slavish adherence to best practices was a worthwhile price to pay if it meant I could produce more perfect code. But I eventually came to realize that terseness can serve as a form of abstraction that can improve code quality, too — enough so to justify straying from best practices sometimes.
In other words: dammit, I want one-liner functions, too!
Regarding a guideline
With the possibility of this
-neutral arrow functions, and terseness being worth pursuit, I offer the following more lenient guideline:
this
.According to the proposal, arrows aimed "to address and resolve several common pain points of traditional Function Expression
." They intended to improve matters by binding this
lexically and offering terse syntax.
However,
this
lexicallyTherefore, arrow functions create opportunities for confusion and errors, and should be excluded from a JavaScript programmer's vocabulary, replaced with function
exclusively.
Regarding lexical this
this
is problematic:
function Book(settings) {
this.settings = settings;
this.pages = this.createPages();
}
Book.prototype.render = function () {
this.pages.forEach(function (page) {
page.draw(this.settings);
}, this);
};
Arrow functions intend to fix the problem where we need to access a property of this
inside a callback. There are already several ways to do that: One could assign this
to a variable, use bind
, or use the 3rd argument available on the Array
aggregate methods. Yet arrows seem to be the simplest workaround, so the method could be refactored like this:
this.pages.forEach(page => page.draw(this.settings));
However, consider if the code used a library like jQuery, whose methods bind this
specially. Now, there are two this
values to deal with:
Book.prototype.render = function () {
var book = this;
this.$pages.each(function (index) {
var $page = $(this);
book.draw(book.currentPage + index, $page);
});
};
We must use function
in order for each
to bind this
dynamically. We can't use an arrow function here.
Dealing with multiple this
values can also be confusing, because it's hard to know which this
an author was talking about:
function Reader() {
this.book.on('change', function () {
this.reformat();
});
}
Did the author actually intend to call Book.prototype.reformat
? Or did he forget to bind this
, and intend to call Reader.prototype.reformat
? If we change the handler to an arrow function, we will similarly wonder if the author wanted the dynamic this
, yet chose an arrow because it fit on one line:
function Reader() {
this.book.on('change', () => this.reformat());
}
One may pose: "Is it exceptional that arrows could sometimes be the wrong function to use? Perhaps if we only rarely need dynamic this
values, then it would still be okay to use arrows most of the time."
But ask yourself this: "Would it be 'worth it' to debug code and find that the result of an error was brought upon by an 'edge case?'" I'd prefer to avoid trouble not just most of the time, but 100% of the time.
There is a better way: Always use function
(so this
can always be dynamically bound), and always reference this
via a variable. Variables are lexical and assume many names. Assigning this
to a variable will make your intentions clear:
function Reader() {
var reader = this;
reader.book.on('change', function () {
var book = this;
book.reformat();
reader.reformat();
});
}
Furthermore, always assigning this
to a variable (even when there is a single this
or no other functions) ensures one's intentions remain clear even after the code is changed.
Also, dynamic this
is hardly exceptional. jQuery is used on over 50 million websites (as of this writing in February 2016). Here are other APIs binding this
dynamically:
this
.this
.this
.EventTarget
with this
.this
.(Stats via http://trends.builtwith.com/javascript/jQuery and https://www.npmjs.com.)
You are likely to require dynamic this
bindings already.
A lexical this
is sometimes expected, but sometimes not; just as a dynamic this
is sometimes expected, but sometimes not. Thankfully, there is a better way, which always produces and communicates the expected binding.
Regarding terse syntax
Arrow functions succeeded in providing a "shorter syntactical form" for functions. But will these shorter functions make you more successful?
Is x => x * x
"easier to read" than function (x) { return x * x; }
? Maybe it is, because it's more likely to produce a single, short line of code. Accoring to Dyson's The influence of reading speed and line length on the effectiveness of reading from screen,
A medium line length (55 characters per line) appears to support effective reading at normal and fast speeds. This produced the highest level of comprehension . . .
Similar justifications are made for the conditional (ternary) operator, and for single-line if
statements.
However, are you really writing the simple mathematical functions advertised in the proposal? My domains are not mathematical, so my subroutines are rarely so elegant. Rather, I commonly see arrow functions break a column limit, and wrap to another line due to the editor or style guide, which nullifies "readability" by Dyson's definition.
One might pose, "How about just using the short version for short functions, when possible?" But now a stylistic rule contradicts a language constraint: "Try to use the shortest function notation possible, keeping in mind that sometimes only the longest notation will bind this
as expected." Such conflation makes arrows particularly prone to misuse.
There are numerous issues with arrow function syntax:
const a = x =>
doSomething(x);
const b = x =>
doSomething(x);
doSomethingElse(x);
Both of these functions are syntactically valid. But doSomethingElse(x);
is not in the body of b
, it is just a poorly-indented, top-level statement.
When expanding to the block form, there is no longer an implicit return
, which one could forget to restore. But the expression may only have been intended to produce a side-effect, so who knows if an explicit return
will be necessary going forward?
const create = () => User.create();
const create = () => {
let user;
User.create().then(result => {
user = result;
return sendEmail();
}).then(() => user);
};
const create = () => {
let user;
return User.create().then(result => {
user = result;
return sendEmail();
}).then(() => user);
};
What may be intended as a rest parameter can be parsed as the spread operator:
processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest
Assignment can be confused with default arguments:
const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens
Blocks look like objects:
(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object
What does this mean?
() => {}
Did the author intend to create a no-op, or a function that returns an empty object? (With this in mind, should we ever place {
after =>
? Should we restrict ourselves to the expression syntax only? That would further reduce arrows' frequency.)
=>
looks like <=
and >=
:
x => 1 ? 2 : 3
x <= 1 ? 2 : 3
if (x => 1) {}
if (x >= 1) {}
To invoke an arrow function expression immediately, one must place ()
on the outside, yet placing ()
on the inside is valid and could be intentional.
(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function
Although, if one writes (() => doSomething()());
with the intention of writing an immediately-invoked function expression, simply nothing will happen.
It's hard to argue that arrow functions are "more understandable" with all the above cases in mind. One could learn all the special rules required to utilize this syntax. Is it really worth it?
The syntax of function
is unexceptionally generalized. To use function
exclusively means the language itself prevents one from writing confusing code. To write procedures that should be syntactically understood in all cases, I choose function
.
Regarding a guideline
You request a guideline that needs to be "clear" and "consistent." Using arrow functions will eventually result in syntactically-valid, logically-invalid code, with both function forms intertwined, meaningfully and arbitrarily. Therefore, I offer the following:
function
.this
to a variable. Do not use () => {}
.Arrow functions or Lambdas, were introduced in ES 6. Apart from its elegance in minimal syntax, most notable functional difference is scoping of this
inside an arrow function
In regular function expressions, the
this
keyword is bound to different values based on the context in which it is called.In arrow functions,
this
is lexically bound, which means it closes overthis
from the scope in which the arrow function was defined (parent-scope), and does not change no matter where and how it is invoked / called.
// this = global Window
let objA = {
id: 10,
name: "Simar",
print () { // same as print: function()
console.log(`[${this.id} -> ${this.name}]`);
}
}
objA.print(); // logs: [10 -> Simar]
objA = {
id: 10,
name: "Simar",
print: () => {
// closes over this lexically (global Window)
console.log(`[${this.id} -> ${this.name}]`);
}
};
objA.print(); // logs: [undefined -> undefined]
In the case of objA.print()
when print()
method defined using regular function
, it worked by resolving this
properly to objA
for method invocation but failed when defined as an arrow=>
function. It is because this
in a regular function when invoked as a method on an object (objA
), is the object itself. However, in case of an arrow function, this
gets lexically bound to the the this
of the enclosing scope where it was defined (global / Window in our case) and stays it stays same during its invocation as a method on objA
.
this
is expected to be fixed & bound at the time definition./* this = global | Window (enclosing scope) */
let objB = {
id: 20,
name: "Paul",
print () { // same as print: function()
setTimeout( function() {
// invoked async, not bound to objB
console.log(`[${this.id} -> ${this.name}]`);
}, 1)
}
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
id: 20,
name: "Paul",
print () { // same as print: function()
setTimeout( () => {
// closes over bind to this from objB.print()
console.log(`[${this.id} -> ${this.name}]`);
}, 1)
}
};
objB.print(); // logs: [20 -> Paul]
In the case of objB.print()
where print()
method is defined as function that invokes console.log(
[${this.id} -> {this.name}])
asynchronously as a call-back on setTimeout
, this
resolved correctly to objB
when an arrow function was used as call-back but failed when call-back was defined as as regular function. It is because arrow =>
function passed to setTimeout(()=>..)
closed over this
lexically from its parent ie. invocation of objB.print()
which defined it. In other-words, the arrow =>
function passed in to to setTimeout(()==>...
bound to objB
as its this
because the in invocation of objB.print()
this
was objB
itself.
We could easily use Function.prototype.bind()
, to make the call-back defined as a regular function work, by binding it to the correct this
.
const objB = {
id: 20,
name: "Singh",
print () { // same as print: function()
setTimeout( (function() {
console.log(`[${this.id} -> ${this.name}]`);
}).bind(this), 1)
}
}
objB.print() // logs: [20 -> Singh]
However, arrow functions come in handy and less error prone for the case of async call-backs where we know the this
at the time of the functions definition to which it gets and should be bound.
Anytime, we need function whose this
can be changed at time of invocation, we can’t use arrow functions.
/* this = global | Window (enclosing scope) */
function print() {
console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
id: 10,
name: "Simar",
print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
id: 20,
name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]
None of the above will work with arrow function const print = () => { console.log(
[${this.id} -> {this.name}]);}
as this
can’t be changed and will stay bound to the this
of the enclosing scope where it was defined (global / Window). In all these examples, we invoked the same function with different objects (obj1
and obj2
) one after the another, both of which were created after the print()
function was declared.
These were contrived examples, but let’s think about some more real life examples. If we had to write our reduce()
method similar to one that works on arrays
, we again can’t define it as a lambda, because it needs to infer this
from the invocation context, ie. the array on which it was invoked
For this reason, constructor
functions can never be defined as arrow functions, as this
for a constructor function can not be set at the time of its declaration. Every-time a constructor function is invoked with new
keyword, a new object is created which then gets bound to that particular invocation.
Also when when frameworks or systems accept a callback function(s) to be invoked later with dynamic context this
, we can’t use arrow functions as again this
may need to change with every invocation. This situation commonly arrises with DOM event handlers
'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
// web-api invokes with this bound to current-target in DOM
this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
// TypeError; 'use strict' -> no global this
this.classList.toggle('on');
});
This is also the reason why in frameworks like Angular 2+ and Vue.js expect the template-component binding methods to be regular function / methods as this
for their invocation is managed by the frameworks for the binding functions. (Angular uses Zone.js to manage async context for invocations of view-template binding functions).
On the other hand, in React, when we want pass a component's method as an event-handler for example <input onChange={this.handleOnchange} />
we should define handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}
as an arrow function as for every invocation, we want this to be same instance of the component that produced the JSX for rendered DOM element.
This article is also aviable on my Medium publication. If you like the artile, or have any comments and suggestions, please clap or leave comments on Medium.
In a simple way,
var a =20; function a(){this.a=10; console.log(a);}
//20, since the context here is window.
Another instance:
var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();
Ans: The console would print 20.
The reason being whenever a function is executed its own stack is created, in this example ex
function is executed with the new
operator so a context will be created, and when inner
is executed it JS would create a new stack and execute the inner
function a global context
though there is a local context.
So, if we want inner
function to have a local context which is ex
then we need to bind the context to inner function.
Arrows solve this problem, instead of taking the Global context
they take the local context
if exists any. In the given example,
it will take new ex()
as this
.
So, in all cases where binding is explicit Arrows solve the problem by defaults.