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.
With () => {}
and function () {}
we are getting two very similar ways to write functions in ES6. In other languages lambda functions often distinguish themselves by being anonymous, but in ECMAScript any function can be anonymous. Each of the two types have unique usage domains (namely when this
needs to either be bound explicitly or explicitly not be bound). Between those domains there is a vast number of cases where either notation will do.
Arrow functions in ES6 have at least two limitations:
- Don't work with
new
- Fixed
this
bound to scope at initialisation
These two limitations aside, arrow functions could theoretically replace regular functions almost anywhere. What is the right approach using them in practice? Should arrow functions be used e.g.:
- "everywhere they work", i.e. everywhere a function does not have to be agnostic about the
this
variable and we are not creating an object. - only "everywhere they are needed", i.e. event listeners, timeouts, that need to be bound to a certain scope
- with 'short' functions but not with 'long' functions
- only with functions that do not contain another arrow function
What I am looking for is a guideline to selecting the appropriate function notation in the future version of ECMAScript. The guideline will need to be clear, so that it can be taught to developers in a team, and to be consistent so that it does not require constant refactoring back and forth from one function notation to another.
Arrow functions were created to simplify function
scope
and solving thethis
keyword by making it more simpler. They utilize the=>
syntax, which looks like an arrow.Note: It does not replace the existing functions. If you replace every function syntax with arrow functions, its not going to work in all cases.
Let's have a look at the existing ES5 syntax, If the
this
keyword were inside an object’s method (a function that belongs to an object), what would it refer to?The above snippet would refer to an
object
and print out the name"RajiniKanth"
. Let's explore the below snippet and see what would this point out here.Now what about if the
this
keyword were inside ofmethod’s function
?Here this would refer to
window object
than theinner function
as its fallen out ofscope
. Becausethis
, always references the owner of the function it is in, for this case — since it is now out of scope — the window/global object.When it is inside of an
object
’s method — thefunction
’s owner is the object. Thus the this keyword is bound to the object. Yet when it is inside of a function, either stand alone or within another method, it will always refer to thewindow/global
object.There are ways to solve this problem in our
ES5
itself, let us look into that before diving into ES6 arrow functions on how solve it.Typically you would, create a variable outside of the method’s inner function. Now the
‘forEach’
method gains access tothis
and thus theobject’s
properties and their values.using
bind
to attach thethis
keyword that refers to the method to themethod’s inner function
.Now with
ES6
arrow function, we can deal withlexical scoping
issue in a simpler way.Arrow functions
are more like function statements, except that theybind
the this toparent scope
. If thearrow function is in top scope
,this
argument will refer towindow/global scope
, while an arrow function inside a regular function will have its this argument the same as its outer function.With
arrow
functionsthis
is bound to the enclosingscope
at creation time and cannot be changed. The new operator, bind, call, and apply have no effect on this.In the above example, we lost the control of this. We can solve the above example by using a variable reference of
this
or usingbind
. With ES6, it becomes easier in managing thethis
as its bound tolexical scoping
.When not to Arrow functions
Inside an object literal.
Actor.getName
is defined with an arrow function, but on invocation it alerts undefined becausethis.name
isundefined
as the context remains towindow
.It happens because the arrow function binds the context lexically with the
window object
... i.e outer scope. Executingthis.name
is equivalent towindow.name
, which is undefined.Object prototype
The same rule applies when defining methods on a
prototype object
. Instead of using an arrow function for defining sayCatName method, which brings an incorrectcontext window
:Invoking constructors
this
in a construction invocation is the newly created object. When executing new Fn(), the context of theconstructor Fn
is a new object:this instanceof Fn === true
.this
is setup from the enclosing context, i.e the outer scope which makes it not assigned to newly created object.Callback with dynamic context
Arrow function binds the
context
statically on declaration and is not possible to make it dynamic. Attaching event listeners to DOM elements is a common task in client side programming. An event triggers the handler function with this as the target element.this
is window in an arrow function that is defined in the global context. When a click event happens, browser tries to invoke the handler function with button context, but arrow function does not change its pre-defined context.this.innerHTML
is equivalent towindow.innerHTML
and has no sense.You have to apply a function expression, which allows to change this depending on the target element:
When user clicks the button, this in the handler function is button. Thus
this.innerHTML = 'Clicked button'
modifies correctly the button text to reflect clicked status.References: https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/
I prefer to use arrow functions at all times where access to local
this
is not needed, because arrow function do not bind their own this, arguments, super, or new.target.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 functionthis
/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:
ES6 version:
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 addreturn
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:
ES6 version:
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:
ES6 version:
Variant 4: When we want to explicitly return from arrow functions.
ES6 version:
Variant 5: When we want to return an object from arrow functions.
ES6 version:
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 forarguments
.ES6 version:
Note:
foo
is an ES5 function, with anarguments
array like object and an argument passed to it is2
soarguments[0]
forfoo
is 2.abc
is an ES6 arrow function since it does NOT have it's ownarguments
hence it printsarguments[0]
offoo
it's outer context instead.Variant 7: Arrow functions do NOT have
this
of their own they depend upon outer context forthis
ES5 version:
Note: The callback passed to setTimeout is an ES5 function and it has it's own
this
which is undefined inuse-strict
environment hence we get output:ES6 version:
Note: The callback passed to
setTimeout
is an ES6 arrow function and it does NOT have it's ownthis
so it takes it from it's outer context that isgreetUser
which hasthis
that isobj6
hence we get output:Miscellaneous: We cannot use
new
with arrow functions. Arrow functions do Not haveprototype
property. We do NOT have binding ofthis
when arrow function is invoked throughapply
orcall
.In a simple way,
Another instance:
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 thenew
operator so a context will be created, and wheninner
is executed it JS would create a new stack and execute theinner
function aglobal context
though there is a local context.So, if we want
inner
function to have a local context which isex
then we need to bind the context to inner function.Arrows solve this problem, instead of taking the
Global context
they take thelocal context
if exists any. In thegiven example,
it will takenew ex()
asthis
.So, in all cases where binding is explicit Arrows solve the problem by defaults.
According to the proposal, arrows aimed "to address and resolve several common pain points of traditional
Function Expression
." They intended to improve matters by bindingthis
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: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 assignthis
to a variable, usebind
, or use the 3rd argument available on theArray
aggregate methods. Yet arrows seem to be the simplest workaround, so the method could be refactored like this:However, consider if the code used a library like jQuery, whose methods bind
this
specially. Now, there are twothis
values to deal with:We must use
function
in order foreach
to bindthis
dynamically. We can't use an arrow function here.Dealing with multiple
this
values can also be confusing, because it's hard to know whichthis
an author was talking about:Did the author actually intend to call
Book.prototype.reformat
? Or did he forget to bindthis
, and intend to callReader.prototype.reformat
? If we change the handler to an arrow function, we will similarly wonder if the author wanted the dynamicthis
, yet chose an arrow because it fit on one line: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
(sothis
can always be dynamically bound), and always referencethis
via a variable. Variables are lexical and assume many names. Assigningthis
to a variable will make your intentions clear:Furthermore, always assigning
this
to a variable (even when there is a singlethis
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 bindingthis
dynamically:this
.this
.this
.EventTarget
withthis
.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 dynamicthis
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" thanfunction (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,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:
Both of these functions are syntactically valid. But
doSomethingElse(x);
is not in the body ofb
, 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 explicitreturn
will be necessary going forward?What may be intended as a rest parameter can be parsed as the spread operator:
Assignment can be confused with default arguments:
Blocks look like objects:
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>=
:To invoke an arrow function expression immediately, one must place
()
on the outside, yet placing()
on the inside is valid and could be intentional.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 usefunction
exclusively means the language itself prevents one from writing confusing code. To write procedures that should be syntactically understood in all cases, I choosefunction
.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:
Guideline for Function Notation in ES6:
function
.this
to a variable. Do not use() => {}
.A while ago our team migrated all its code (a mid-sized AngularJS app) to JavaScript compiled using
TraceurBabel. I'm now using the following rule of thumb for functions in ES6 and beyond:function
in the global scope and forObject.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-higherfunction
statement to see what thethisObject
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 (thinkString.prototype.truncate
etc.) and those generally have to be of typefunction
anyway. Consistently usingfunction
on the global scope helps avoid errors.function foo(){}
thanconst 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:
One key advantage of functions over arrow functions is therefore that functions double as object constructors:
However, the functionally identical2 ES Harmony draft class definition is almost as compact:
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 aPromise
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:
The same piece of code with regular functions:
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 theObject.prototype
. In the mean time I suggest reserving thefunction
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.