Mastering the Arcane Art Of JavaScript-Mancy for C# Developers: ES6 Arrow Functions
The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.
It is time to continue upgrading your JavaScript-Fu to the next level of mastery! This time I present to you ES6 arrow functions, a way to bring the beauty of C# lambda expressions to JavaScript.
The Arrow Function
Hi! You can experiment with all the examples in this article directly within this jsBin.
Arrow functions are a great feature in ES6 that feel very much like C# lambda expressions. They give you a beautiful and terse syntax that let’s you write:
> let createWater = mana => `${mana} liters of water`;
> console.log(createWater(10));
// => 10 liters of water
Instead of the more lengthy anonymous function expression:
let createWater = function(mana) {
return `${mana} liters of water`
}
The above example of arrow function has an implicit return
statement that returns the expression to the right of the fat arrow =>
. This simple notation is only valid if the body of your arrow function has a single statement.
Like in C# you’ll often see arrow functions used in conjunction with array methods such as filter
(the JavaScript version of LINQ’s Where
):
let monsters = ['orc chieftain', 'orc grunt', 'small orc', 'goblin']
let orcs = monsters.filter(m => m.includes('orc'))
console.log(orcs)
// => ["orc chieftain", "orc grunt", "small orc"]
You can define arrow functions with any number of arguments. For instance, you can have no arguments at all:
> let helloMiddleEarth = () => "hello Middle Earth!";
> console.log(helloMiddleEarth());
// => hello Middle Earth!
Or one:
let frodo = {
toString() {
return 'Frodo'
},
destroyTheOneRing() {
console.log(`${this} throws the one ring into the entrails of Mount Doom`)
},
hideFrom(enemy, how) {
console.log(`${this} hides from the ${enemy} ${how}`)
},
}
let destroyDaRing = hobbit => hobbit.destroyTheOneRing()
destroyDaRing(frodo)
// => Frodo throws the one ring into the entrails of Mount Doom
Two:
let nazgul = {
toString(){ return 'scary nazgul';}
};
let useElvenCloak = (hobbit, enemy)
=> hobbit.hideFrom(enemy, 'with an elven cloak');
useElvenCloak(frodo, nazgul);
// => Frodo hides from the scary nazgul with an elven cloak
Or as many arguments as you want using the rest syntax:
useElvenCloak = (hobbit, ...enemies)
=> hobbit.hideFrom(enemies, 'with an elven cloak');
useElvenCloak(frodo, nazgul, 'orc', 'troll');
// => Frodo hides from the scary nazgul,orc,troll with an elven cloak
Because they are just functions you can also use defaults (in this case hobbit=frodo
):
destroyDaRing = (hobbit = frodo) => hobbit.destroyTheOneRing()
destroyDaRing()
// => Frodo throws the one ring into the entrails of Mount Doom
And destructuring:
let companyOfTheRing = {
smartestHobbit: frodo,
wizard: 'Gandalf',
ranger: 'Aragorn',
// etc
}
destroyDaRing = ({ smartestHobbit }) => smartestHobbit.destroyTheOneRing()
destroyDaRing(companyOfTheRing)
// => Frodo throws the one ring into the entrails of Mount Doom
If the body of your arrow function has more than one statement then you’ll need to wrap it inside curly braces just like you would do in C#:
let eatRation = (hobbit, rations) => {
let ration = rations.shift()
if (ration) {
hobbit.hp += ration.hp
console.log(`${hobbit} eats ${ration} and recovers ${ration.hp} hp`)
} else {
console.log(`There are no rations left! We're all gonna die!!!!`)
}
}
let rations = [
{
name: 'sandwich',
hp: 5,
toString() {
return this.name
},
},
]
eatRation(frodo, rations)
// => Frodo eats sandwich and recovers 5 hp
In this case when you have more than one statement you need to return
a value explicitly:
let carveWood = (wood, shape) => {
console.log(`You carve a piece of ${wood} into a ${shape}`)
return { name: shape, material: wood }
}
let pipe = carveWood('oak', 'pipe')
// => You carve a piece of oak into a pipe
An arrow function can also return an object via the object initializer syntax. When returning a new object like this, you’ll need to wrap it inside parenthesis (so that the JavaScript runtime understands that it is indeed an object and not a block of code):
let createHealthPotion = () => ({
name: 'potion of health',
hp: 10,
toString() {
return `${this.name} (+${this.hp}hp)`
},
})
let healthPotion = createHealthPotion()
console.log(healthPotion.toString())
// => potion of Health (+10 hp)
Arrow Functions Arcana
Arrow functions provide a terser syntax that function expressions but as functions themselves they are a little bit special, and when I say a little I mean a lot:
- They don’t have
this
- They don’t have an
arguments
object - You cannot use
bind
,apply
andcall
to set the context in which they are evaluated - You cannot use
new
norsuper
Indeed if you look at the ECMA-262 spec:
14.2.16 Arrow Functions - Runtime Semantics: Evaluation
An ArrowFunction does not define local bindings for
arguments
,super
,this
, ornew.target
. Any reference toarguments
,super
,this
, ornew.target
within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.Read more within the spec at http://bit.ly/es6-spec
But what does it mean for arrow functions to not have their own version of this
nor arguments
?
Well it means that when you refer to this
or arguments
within an arrow function you are actually referring to this
or arguments
in the enclosing environment. Let’s clarify this with an example.
Let’s say that we have gollum
that wants to be pleasant before he stabs you in the back and steals your wedding ring. If we use normal functions to define his greetings:
let gollum = {
name: 'Golum! Golum!',
toString() {
return `${this.name}!!!`
},
saysHi() {
console.log(`Hi! I am ${this}`)
setTimeout(function() {
console.log(`${this} stabs you in the back and
steals your wedding ring while saying 'My Preciouuuuuus'`)
}, /*waitPeriodInMilliseconds*/ 500)
},
}
Then call the function saysHi
:
// call it in the context of the gollum object
gollum.saysHi();
// => Hi! I am Gollum! Gollum!!!!
// => "[object Window] stabs you in the back and
steals your wedding ring while saying 'My Preciouuuuuus'"
/ => Hi! I am Gollum! Gollum!!!!
As we expected gollum
happily salutes us and after a while stabs us in the back. The only problem being that it is gollum
no longer but the Window
object. Nothing new. We learned about this strange behavior in Mastering the Arcane Art of JavaScript-mancy for C# Developers - Chapter 1: The Many a One JavaScript Quirks. But what happens if we use an arrow function instead of a normal function?
// what happens if we use an arrow function instead?
let gollumWithArrowFunctions = {
name: 'Golum! Golum!',
toString() {
return `${this.name}!!!`
},
saysHi() {
console.log(`Hi! I am ${this}`)
setTimeout(
() =>
console.log(`${this} stabs you in the back and
steals your wedding ring while saying 'My Preciouuuuuus'`),
/*waitPeriodInMilliseconds*/ 500
)
},
}
In this example we have redefined the saysHi
function to use an arrow function as callback within setTimeout
instead of a normal function. And if we call it:
gollumWithArrowFunctions.saysHi();
// => Hi! I am Gollum! Gollum!!!!
// => Golum! Golum!!!! stabs you in the back and
steals your wedding ring while saying 'My Preciouuuuuus'
The arrow function guarantees that the right version of this
is used. What is happening? Because the arrow function doesn’t have its own version of this
it accesses the this
defined by the saysHi
method (effectively behaving like a closure). Because saysHi
was called using the dot notation gollumWithArrowFunctions.saysHi
then the object itself is the value of this
and thus eveything works and we die an ignominious death at Gollum’s hands.
What are the consequences of this? You may be asking yourself. Well, the most exciting consequence of an arrow function not having its own this
is that it makes them more resistant to the problems with this
that you saw in The Many A One JavaScript Quirks
Let’s bring this concept home using yet another example, the same one we used in The Many A One JavaScript Quirks:
function UsersCatalogJQuery() {
'use strict'
var self = this
this.users = []
getUsers()
function getUsers() {
$.getJSON('https://api.github.com/users').success(function(users) {
// console.log(users);
// console.log(this);
try {
this.users.push(users)
} catch (e) {
console.log(e.message)
}
// BOOOOOOOM!!!!!
// => Uncaught TypeError: Cannot read property 'push' of undefined
// 'this' in this context is the jqXHR object
// not our original object
// that's why we usually use a closure here instead:
// self.products = products;
})
}
}
var catalog = new UsersCatalogJQuery()
Again, if you substitute the anonymous function expression for an arrow function you solve the problem! And in a much elegant way than binding the function explicitly (with bind
) or by using a closure (var self = this
):
function UsersCatalogJQueryArrowFunction() {
'use strict'
this.users = []
this.getUsers = function getUsers() {
$.getJSON('https://api.github.com/users').success(users =>
this.users.push(users)
) // hello arrow function
// this is mostly equivalent to:
// .success(function(users){return this.users.push(users);}.bind(this))
}
this.getUsers()
}
var catalog = new UsersCatalogJQueryArrowFunction()
Arrow Functions And This Gotchas
Now that we’ve learned about the good parts of the arrow function and how it can help us write terser code and avoid some problems with the this
keyword let’s take a look at its darker sides: when an arrow function doesn’t behave like a function.
ECMA-262 Function.prototype.bind and Arrow Functions
If Target is an arrow function or a bound function then the
thisArg
passed to this method will not be used by subsequent calls to the function
You cannot use bind
with arrow functions. If you try to bind an arrow function to an object it won’t work. Let’s illustrate this with an example:
let saruman = {
name: 'Saruman, the White',
toString() {
return this.name
},
raiseUrukhai() {
console.log(`${this} raises a Urukhai from the pits of Isengard`)
return { name: 'Uruk', hp: 500, strength: 18 }
},
telekineticStaffAttack: () =>
console.log(`${this} uses his staff to throw
you across the room "telekinetically" speaking`),
}
In this example we have saruman
, another epic javascriptmancer, with a couple of methods. One raiseUrukhai
is a regular function, the other telekineticStaffAttack
uses an arrow function. If we call these methods using the dot notation:
saruman.raiseUrukhai();
// => Saruman, the White raises a Urukhai from the pits of Isengard
saruman.telekineticStaffAttack();
// => [object Window] uses his staff to throw
you across the room \"telekinetically\" speaking
// this would be undefined instead of Window if we used strict mode
If you look at the output of the telekineticStaffAttack
method you may be confused to see the Window
object as this
instead of saruman
himself. You have to remember that arrow functions have no this
and so, when you use this
inside an arrow function, you refer to this
in the enclosing environment. In this case, because the arrow function is defined within an object in turn defined within the global scope, the closer this
is the Window
object (or undefined
in case of strict mode). Again, the rules we learned about this
in The Many a One JavaScript Quirks don’t apply to arrow functions and calling an arrow function using the dot notation doesn’t evaluate the arrow function in the context of the object.
If we try to use bind
to bind these two methods to a different object:
// if we try to bind these two methods to a new object
let boromir = {
name: 'Boromir of Gondor',
toString() {
return this.name
},
}
let raiseUrukhaiBound = saruman.raiseUrukhai.bind(boromir)
raiseUrukhaiBound()
// => Boromir of Gondor raises a Urukhai from the pits of Isengard
We can appreciate how we can bind a normal function but when we try to bind an arrow function:
let telekineticStaffAttackBound = saruman.telekineticStaffAttack.bind(boromir);
telekineticStaffAttackBound();
// => undefined uses his staff to throw
you across the room \"telekinetically\" speaking
// didn't work, not telekinetic staff attack for Boromir
Nothing happens. Since an arrow function doesn’t have this
it makes no sense to bind
it.
Even though arrow functions are not the same as bound functions, once an arrow function is declared and encloses its nearest this
it pretty much behaves in the same way as a bound function.
let Warg = function(name, size){
this.name = name;
this.size = size;
this.name = '${name}, the ${size} warg`;
// wargs don't bark, they wark
this.wark = () => console.log(`${name} warks!: Wark! Wark!`);
this.jump = (function(){
console.log(`${name} jumps around`);
}).bind(this);
}
let willyTheWarg = new Warg('willy', 'litte');
In this example I am using a constructor function and the new keyword to instantiate a fiery warg. We haven’t seen any of those concepts yet since I am reserving it for the OOP section of the series. They are still the best way to exemplify the similar behavior of arrow functions and bound functions so I hope you’ll forgive me. Essentially you use the new
keyword to instantiate new objects via constructor functions. When you apply the new keyword on any function the JavaScript runtime instantiates an object {}
, sets it as the this
value of the function, then evaluates the function and finally returns it. This is useful because it is the this
value that the wark
method is going to enclose and safeguard for the rest of the program execution.
After creating willyTheWarg
we got ourselves an arrow function wark
and a bound function jump
. If we execute any of them we will be able to appreciate how this
refers to the warg itself:
// this is an arrow function
willyTheWarg.wark()
// => willy, the litte warg warks!: Wark! Wark!
// and this is the bound function
willyTheWarg.jump()
// => willy jumps around
This is the expected behavior, but what happens if we are mean and take these functions away from willyTheWarg
?
Well the bound function, as we learned in JavaScript Quirks, will still have willyTheWarg
as its context:
let jump = willyTheWarg.jump
jump()
// => willy, the litte warg warks!: Wark! Wark!
let goblin = { jump: jump }
goblin.jump()
// => willy, the litte warg warks!: Wark! Wark!
And the arrow function behaves in exact the same way, but instead of being explicitely bound to willyTheWarg
it is implicitly bound by the closure over the this
variable.
let wark = willyTheWarg.wark
wark()
// => willy, the litte warg warks!: Wark! Wark!
goblin.wark = wark
goblin.wark()
// => willy, the litte warg warks!: Wark! Wark!
This similar behavior and the fact that neither bound nor arrow functions can be bound (re-bound in the case of the bound function) makes both types of function practically identical in this context. The only different being that bound functions don’t need a closure, you can just bind a normal function to whatever object you want by just calling the bind
method. Arrow functions on the other hand can be seen to be implicitly bound to their enclosing context by virtue of the closure.
ECMA-262 Function.prototype.apply, Function.prototype.call and Arrow Functions
If func is an arrow function or a bound function then the
thisArg
will be ignored by the function[[Call]]
in step 6.If func is an arrow function or a bound function then the
thisArg
will be ignored by the function[[Apply]]
in step 5.
In addition to bind
, you cannot use call
nor apply
on an arrow function to change its context. If you remember JavaScript Quirks, you can use call
and apply
to explicitly set the context in which a function is executed, that is, the value of this
:
let caragor = {
toString() {
return 'scary caragor'
},
}
let howl = function({ times }) {
console.log(`${this} howls to the moon ${times} times!`)
}
// a normal function let's you set its context explicitly via apply or call
howl.apply(caragor, [{ times: 3 }])
// => scary caragor howls to the moon 3 times!
howl.call(caragor, { times: 4 })
// => scary caragor howls to the moon 4 times!
But if you try to use either apply
or call
with an arrow function, the context that you pass as argument will be completely ignored:
// an *arrow function* completely ignores the value of `this` passed as argument
willyTheWarg.wark.apply(caragor)
// => willy, the litte warg warks!: Wark! Wark!
willyTheWarg.wark.call(caragor)
// => willy, the litte warg warks!: Wark! Wark!
In the example above you can easily appreciate how instead of scary caragor
the ${this}
within the wark
arrow function is evaluated as willy, the little
. This demostrates how arrow functions ignore the context when called with either call
or apply
.
Arrow Functions And Arguments Gotchas
ECMA-262 FunctionDeclarationInstantiation
Arrow functions never have an
arguments
objects.
Another interesting feature of arrow functions is that they don’t have arguments
object. What does that mean? Just like with this
if you attempt to access the arguments
object within an arrow function you’ll access the arguments
of the enclosing environment.
If you remember More Useful Function Patterns - Multiple Arguments every function in JavaScript has a arguments
object that you can use to access which arguments where passed to a function. So if you have a normal function that logs the arguments
object:
function rememberWhatISaid() {
console.log(`you said: ${Array.from(arguments).join(', ')}`)
}
You can demonstrate how the arguments
object collects those arguments being passed to the function:
rememberWhatISaid('hello', 'you', 'there')
// => you said: hello, you, there
rememberWhatISaid('supercalifragilisticusespialidosus')
// => you said: supercalifragilisticusespialidosus
Not so with arrow functions:
let forgetWhatISaid = () => {
console.log(`I am going to forget that you said: ${arguments}`)
}
forgetWhatISaid('I said Wazzaaaaa')
// => error ReferenceError: arguments is not defined
The arguments
variable is not defined and thus we get a ReferenceError
. Let’s define it and see what happens:
let arguments = ['trying something crazy']
let forgetWhatISaid = () => {
console.log(`I am going to forget that you said: ${arguments}`)
}
forgetWhatISaid('I said Wazzaaaaa')
// => I am going to forget that you said: trying something crazy
We can also make the same experiment wrapping an arrow function inside another function:
function createMemoryWisp() {
return () =>
console.log(`*MemoryWisp*: You said...
${Array.from(arguments).join(', ')}`)
}
let wispRememberThis = createMemoryWisp(1, 2, 3, 4, 'banana!')
wispRememberThis('important password', '123456789')
// => *MemoryWisp*: You said... 1, 2, 3, 4, banana!
So as you can see in both these examples, arrow functions don’t have their own arguments
object and use the arguments
object of their enclosing environment. But What if we want to send an arbitrary number or arguments to an arrow function? Well in that case you should use the rest operator:
function createMemoryWispWithRest() {
return (...thingsToRemember) =>
console.log(`*MemoryWisp*: You said... ${thingsToRemember.join(', ')}`)
}
let wispRememberThisAgain = createMemoryWispWithRest()
wispRememberThisAgain('important password', '123456789')
// => *MemoryWisp*: You said... important password, 123456789
In summary, whenever you use arguments
inside an arrow function you’ll be accessing the enclosing environment’s arguments
object. So if you want to send multiple arbitrary arguments to an arrow function use the rest operator.
Arrow Functions and the New and Super Operators
The new
and super
operators are two operators that we will see in depth in the OOP section of the series. The new
operator lets you create new instances of objects when applied to a any function which will act as a constructor. The super
keyword is new in ES6 and lets you access methods in parent objects within an inheritance chain.
In much the same way as with bind
, call
and apply
, you cannot use new
nor super
with an arrow function.
Concluding
In this article you learned about arrow functions which resemble lambdas in C#. Arrow functions let you use a terser syntax than the normal function syntax and help you avoid problems with the this
keyword by using the this
value of their enclosing environment.
Arrow functions are a little bit special in what regards to this
since they are the only functions in JavaScript that don’t have their own this
value. Because of this characteristic you cannot use bind
, call
or apply
to specify the context in which an arrow function will be evaluated. In a similar fashion you cannot use the new
and super
operators with an arrow function.
Additionally, arrow functions don’t have their own arguments
object, if you try to access arguments
inside an arrow function you’ll access the arguments
object within the enclosing function. This means that if you want for an arrow function to take an arbitrary number of arguments you’ll need to use the rest syntax.
Interested in Learning More JavaScript? Buy the Book!
Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.
Have a great week ahead!! :)
More Articles In This Series
- Chapter 0: The Basic Ingredients of JavaScriptMancy
- Chapter 1: The Many a One JavaScript Quirks
- Functions
- Object Oriented Programming
- Basics
- ES6
- OOP
- Introduction to OOP in JavaScript for C# Developers
- Summoning Fundamentals: Introduction to OOP In JavaScript – Encapsulation
- Summoning Fundamentals: Introduction to OOP In JavaScript – Inheritance
- Summoning Fundamentals: Introduction to OOP In JavaScript – Polymorphism
- White Tower Summoning: Mimicking C# Classical Inheritance in Javascript
- White Tower Summoning Enhanced: The Marvels of ES6 Classes
- Black Tower Summoning: Object Composition with Mixins
- Safer JavaScript Composition With Traits and Traits.js
- JavaScript Ultra Flexible Object Oriented Programming with Stamps
- Object Oriented JavaScript for C# Programmers
- Tooling
- Data Structures
- Functional Programming
- And more stuff to be shaped out:
- Functional Programming with JavaScript
- Modules
- Asynchronous Programming
- The JavaScript Ecosystem
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.