Upgrading Your Everyday JavaScript Magic With ES6 - Arrow Functions
Speak less.
That way there's less chance
that you'll say something stupid.
Think about that,
when you craft your next spell.
- Kyeich Chir
Guardian of the word
Behold! The Arrow Function!
Arrow functions are one of my favorite features in ES6. They give you a beautiful and terse way to write JavaScript functions which is reminiscent of C# lambda expressions.
Here you have a vanilla JavaScript function expression:
1 let createWater = function (mana){
2 return `${mana} liters of water`;
3 }
And here you have an equivalent version as an arrow function:
1 let createWater = mana => `${mana} liters of water`;
If you call any of these functions you’ll get the same result:
1 console.log(createWater(10));
2 // => 10 liters of water
Let’s examine the arrow function in closer quarters. You may have noticed that it doesn’t have any return statement. And that’s because in its simplest incarnation, the arrow function has an implicit return that returns whichever expression is to the right of the fat arrow =>. Note that this notation is only valid when an 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):
1 let monsters = ['orc chieftain', 'orc grunt', 'small orc', 'goblin'];
2 let orcs = monsters
3 .filter(m => m.includes('orc'));
4 console.log(orcs);
5 // => ["orc chieftain", "orc grunt", "small orc"]
You can define arrow functions with any arbitrary number of arguments. For instance, you can have no arguments at all:
1 let helloMiddleEarth = () => "hello Middle Earth!";
2
3 console.log(helloMiddleEarth());
4 // => hello Middle Earth!
Or one:
1 let frodo = {
2 toString(){ return 'Frodo'},
3 destroyTheOneRing() {
4 console.log(`${this} throws the one ring into the entrails of Mo\
5 unt Doom`);
6 },
7 hideFrom(enemy, how) {
8 console.log(`${this} hides from the ${enemy} ${how}`);
9 }
10 };
11
12 let destroyDaRing = (hobbit) => hobbit.destroyTheOneRing();
13
14 destroyDaRing(frodo);
15 // => Frodo throws the one ring into the entrails of Mount Doom
Two:
1 let nazgul = {
2 toString(){ return 'scary nazgul';}
3 };
4 let useElvenCloak = (hobbit, enemy)
5 => hobbit.hideFrom(enemy, 'with an elven cloak');
6
7 useElvenCloak(frodo, nazgul);
8 // => Frodo hides from the scary nazgul with an elven cloak
Or as many arguments as you want using the rest syntax:
1 useElvenCloak = (hobbit, ...enemies)
2 => hobbit.hideFrom(enemies, 'with an elven cloak');
3 useElvenCloak(frodo, nazgul, 'orc', 'troll');
4 // => Frodo hides from the scary nazgul,orc,troll with an elven cloak
Because they are just functions you can also use defaults:
1 destroyDaRing = (hobbit=frodo) => hobbit.destroyTheOneRing();
2 destroyDaRing();
3 // => Frodo throws the one ring into the entrails of Mount Doom
And destructuring:
1 let companyOfTheRing = {
2 smartestHobbit: frodo,
3 wizard: 'Gandalf',
4 ranger: 'Aragorn',
5 // etc
6 };
7 destroyDaRing =
8 ({smartestHobbit}) => smartestHobbit.destroyTheOneRing();
9
10 destroyDaRing(companyOfTheRing);
11 // => 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#:
1 let eatRation = (hobbit, rations) => {
2 let ration = rations.shift();
3 if (ration) {
4 hobbit.hp += ration.hp;
5 console.log(`${hobbit} eats ${ration} and ` +
6 `recovers ${ration.hp} hp`);
7 } else {
8 console.log(`There are no rations left! We're all gonna die!!`);
9 }
10 }
11
12 let rations = [{
13 name: 'sandwich',
14 hp: 5,
15 toString(){ return this.name;}
16 }];
17
18 eatRation(frodo, rations);
19 // => Frodo eats sandwich and recovers 5 hp
Additionally, when you have more than one statement you’ll need to return a value explicitly:
1 let carveWood = (wood, shape) => {
2 console.log(`You carve a piece of ${wood} into a ${shape}`);
3 return {name: shape, material: wood};
4 }
5 let pipe = carveWood('oak', 'pipe');
6 // => You carve a piece of oak into a pipe
An arrow function can also return an object via the object initializer syntax. When doing so, you’ll need to wrap it inside parentheses. That way the JavaScript runtime will be able to understand that it is an object and not a block of code:
1 let createHealthPotion = () => ({
2 name: 'potion of health',
3 hp: 10,
4 toString(){
5 return `${this.name} (+${this.hp}hp)`;
6 }});
7 let healthPotion = createHealthPotion();
8 console.log(healthPotion.toString());
9 // => potion of Health (+10 hp)
In summary, arrow functions are awesome. Using arrow functions you’ll be able to write much terser code and still get to use features like destructuring or defaults. But as functions themselves they are a little bit special, and when I say a little I mean a lot.
Arrow Functions Arcana
Indeed, though they seem like regular functions, arrow functions have their quirks:
- They don’t have
this - They don’t have an
argumentsobject - You cannot use
bind,applyandcallto set the context in which they are evaluated - You cannot use
newnorsuper
This may be surprising but if you take a look at the ECMA-262 specification22, that is, JavaScript’s own specification, you’ll read the following:
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.targetwithin an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.
But what does it exactly mean for arrow functions not to have their own version of this nor arguments?
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 to you just before he stabs you in the back and steals your wedding ring. If we use normal functions to define his greetings:
1 let gollum = {
2 name: 'Golum! Golum!',
3 toString(){ return `${this.name}!!!`;},
4 saysHi(){
5 console.log(`Hi! I am ${this}`);
6 setTimeout(function(){
7 console.log(`${this} stabs you in the back and
8 steals your wedding ring while saying 'My Preciouuuuuus'`)
9 },/*waitPeriodInMilliseconds*/ 500);
10 }
11 };
And then call the function saysHi:
1 // call it in the context of the gollum object
2 gollum.saysHi();
3 // => Hi! I am Gollum! Gollum!!!!
4 // => "[object Window] stabs you in the back and
5 // steals your wedding ring while saying 'My Preciouuuuuus'"
As we expected, gollum happily salutes us. Then, after a short while, he returns and stabs us in the back. The only problem being that it is no longer gollum but the Window object. This is nothing new. We learned about this strange behavior of this in Mysteries of the JavaScript Arcana. But what happens if we use an arrow function instead of a normal function?
1 // what happens if we use an arrow function instead?
2 let gollumWithArrowFunctions = {
3 name: 'Golum! Golum!',
4 toString(){ return `${this.name}!!!`;},
5 saysHi(){
6 console.log(`Hi! I am ${this}`);
7 setTimeout(() =>
8 console.log(`${this} stabs you in the back and
9 steals your wedding ring while saying 'My Preciouuuuuus'`)
10 ,/*waitPeriodInMilliseconds*/ 500);
11 }
12 };
Notice how we have rewritten the saysHi function to use an arrow function within setTimeout instead of a normal function. If we call it:
1 gollumWithArrowFunctions.saysHi();
2 // => Hi! I am Gollum! Gollum!!!!
3 // => Golum! Golum!!!! stabs you in the back and
4 // 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 everything works yey! And we die an ignominious death at Gollum’s hands. No!
What are the consequences of this? Well, the most exciting consequence of an arrow function not having its own this is that it makes them more resistant to the this problems you saw in Mysteries of the JavaScript Arcana (they are more resistant but not bullet proof as you’ll see later in this chapter).
Let’s bring this concept home with another example, the same one we used in Mysteries of the JavaScript Arcana:
1 function UsersCatalogJQuery(){
2 "use strict";
3 var self = this;
4
5 this.users = [];
6 getUsers()
7
8 function getUsers(){
9 $.getJSON('https://api.github.com/users')
10 .success(function updateUsers(users){
11 // console.log(users);
12 // console.log(this);
13 try {
14 this.users.push(users);
15 } catch(e) {
16 console.log(e.message);
17 }
18 // BOOOOOOOM!!!!!
19 // => Uncaught TypeError:
20 // Cannot read property 'push' of undefined
21 // 'this' in this context is the jqXHR object
22 // not our original object
23 // that's why we usually use a closure here instead:
24 // self.products = products;
25 });
26 }
27 }
28 var catalog = new UsersCatalogJQuery();
Again, if you take advantage of arrow functions and substitute the updateUsers function expression for an arrow function you’ll solve the problem. And in a more elegant way than binding the function explicitly (with bind) or using a closure (var self = this). You can appreciate that elegance in this example below:
1 function UsersCatalogJQueryArrowFunction(){
2 "use strict";
3 this.users = [];
4 this.getUsers = function getUsers(){
5 $.getJSON('https://api.github.com/users')
6 .success(users => this.users.push(users)); // arrow function
7 // this is mostly equivalent to:
8 // .success(function(users){
9 // return this.users.push(users);}.bind(this))
10 };
11
12 this.getUsers();
13 }
14 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.
Let’s start with the not having this and when it can become a problem.
Beware of Using Arrow Functions With Object Literals
My first thought when I learned about arrow functions was: Awesome! Now I can use arrow functions everywhere! And so I wrote this:
1 let raistlin = {
2 name: 'Raistlin',
3 toString(){ return this.name;},
4 deathRay: () =>
5 console.log(`${this} casts a terrible ray of deaaaath!`)
6 }
To my surprise, when I tried to have raistlin cast an evil death ray this is what happened:
1 raistlin.deathRay();
2 // => [object Window] casts a terrible ray of deaaaath!
The arrow function, which we had thought impervious to this problems before, now reveals us the classic this issue.
What is happening here? Well, if you remember from the previous section arrow functions don’t have this. Because of that, when you try to access this inside an arrow function you’re referring to the this of the outer scope. In the case of an object literal, the this from the outer scope is no other than the Window object. Again, the rules we learned about this in Mysteries of the JavaScript Arcana don’t apply to arrow functions and calling an arrow function using the dot notation doesn’t evaluate it in the context of the object. So remember, be wary of using arrow functions as properties of object literals.
You Can’t Bind Arrow Functions
You cannot use bind with arrow functions. If you are brave enough to try to bind an arrow function to an object you’ll be sorely disappointed because it won’t work.
Let’s illustrate it with an example:
1 let saruman = {
2 name: 'Saruman, the White',
3 toString(){ return this.name;},
4 raiseUrukhai(){
5 console.log(`${this} raises a Urukhai from the pits ` +
6 ` of Isengard`);
7 return {name: 'Uruk', hp: 500, strength: 18};
8 },
9 telekineticStaffAttack: () =>
10 console.log(`${this} uses his staff to throw
11 you across the room "telekinetically" speaking`)
12 }
Behold saruman! Another epic javascriptmancer. He has a couple of methods that show his magic prowess. One raiseUrukhai is a regular function, the other telekineticStaffAttack uses an arrow function. If we call these methods using the dot notation:
1 saruman.raiseUrukhai();
2 // => Saruman, the White raises a Urukhai from the pits of Isengard
3
4 saruman.telekineticStaffAttack();
5 // => [object Window] uses his staff to throw
6 // you across the room "telekinetically" speaking
7 // this would be undefined instead of Window if we used
8 // strict mode
Again, just like we saw in the previous section, when we call telekineticStaffAttack method the this gets evaluated as Window. But let’s say that we want to solve it as we are accustomed to by using bind.
If we use bind to bind these two methods to a different object:
1 // if we try to bind these two methods to a new object
2 let boromir = {name: 'Boromir of Gondor',
3 toString(){return this.name;}};
4 let raiseUrukhaiBound = saruman.raiseUrukhai.bind(boromir);
5 raiseUrukhaiBound();
6 // => 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 nothing happens:
1 let telekineticStaffAttackBound =
2 saruman.telekineticStaffAttack.bind(boromir);
3
4 telekineticStaffAttackBound();
5 // => undefined uses his staff to throw
6 // you across the room "telekinetically" speaking
7 // didn't work, not telekinetic staff attack for Boromir
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’s illustrate this idea with some code:
1 // this is a constructor function
2 let Warg = function(name, size){
3 this.name = name;
4 this.size = size;
5 this.name = '${name}, the ${size} warg`;
6 // wargs don't bark, they wark
7 this.wark = () => console.log(`${name} warks!: Wark! Wark!`);
8 this.jump = (function(){
9 console.log(`${name} jumps around`);
10 }).bind(this);
11 }
12
13 // here we are creating a new object using the new operator
14 let willyTheWarg = new Warg('willy', 'litte');
In this example we are using a constructor function and the new keyword to instantiate a fiery warg. Even though we haven’t seen any of these concepts yet because I am reserving them for the OOP section of the series, they are still the best way to exemplify the similar behavior of arrow functions and bound functions (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:
1 // this is an arrow function
2 willyTheWarg.wark();
3 // => willy, the litte warg warks!: Wark! Wark!
4
5 // and this is the bound function
6 willyTheWarg.jump();
7 // => 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 Mysteries of the JavaScript Arcana, will still have willyTheWarg as its context:
1 let jump = willyTheWarg.jump;
2 jump();
3 // => willy, the litte warg warks!: Wark! Wark!
4
5 let goblin = {jump: jump};
6 goblin.jump();
7 // => willy, the litte warg warks!: Wark! Wark!
And the arrow function behaves in exact the same way. Instead of being explicitely bound to willyTheWarg it is implicitly bound by the closure over the this variable:
1 let wark = willyTheWarg.wark;
2 wark();
3 // => willy, the litte warg warks!: Wark! Wark!
4
5 goblin.wark = wark;
6 goblin.wark();
7 // => 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 situation. The only difference 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.
You Can’t Use Apply or Call on Arrow Functions
In addition to bind, you cannot use call nor apply on an arrow function to change its context. If you remember Mysteries of the JavaScript Arcana, you can use call and apply to explicitly set the context in which a function is executed, that is, the value of this:
1 let caragor = {toString(){return 'scary caragor';}}
2 let howl = function({times}){
3 console.log(`${this} howls to the moon ${times} times!`);
4 }
5 // a normal function let's you set its context explicitly via apply \
6 or call
7 howl.apply(caragor, [{times: 3}]);
8 // => scary caragor howls to the moon 3 times!
9 howl.call(caragor, {times: 4});
10 // => 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:
1 // an *arrow function* completely ignores the value of `this` passed\
2 as argument
3 willyTheWarg.wark.apply(caragor);
4 // => willy, the litte warg warks!: Wark! Wark!
5 willyTheWarg.wark.call(caragor);
6 // => 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 Don’t Have Arguments Object
Another interesting feature of arrow functions is that they don’t have arguments object. 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:
1 function rememberWhatISaid(){
2 console.log(`you said: ${Array.from(arguments).join(', ')}`);
3 }
You can easily demonstrate how the arguments object collects those arguments being passed to the function:
1 rememberWhatISaid('hello', 'you', 'there');
2 // => you said: hello, you, there
3 rememberWhatISaid('supercalifragilisticusespialidosus')
4 // => you said: supercalifragilisticusespialidosus
Not so with arrow functions:
1 let forgetWhatISaid = () => {
2 console.log(`I am going to forget that you said: ${arguments}`);
3 }
4 forgetWhatISaid('I said Wazzaaaaa');
5 // => 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:
1 let arguments = ['trying something crazy'];
2 let forgetWhatISaid = () => {
3 console.log(`I am going to forget that you said: ${arguments}`);
4 }
5 forgetWhatISaid('I said Wazzaaaaa');
6 // => 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:
1 let createMemoryWisp(){
2 return () => console.log(`*MemoryWisp*: You said...
3 ${Array.from(arguments).join(', ')}`);
4 }
5 let wispRememberThis = createMemoryWisp(1, 2, 3, 4, 'banana!');
6 wispRememberThis('important password', '123456789');
7 // => *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:
1 function createMemoryWispWithRest(){
2 return (...thingsToRemember) =>
3 console.log(`*MemoryWisp*: You said... ${thingsToRemember.jo\
4 in(', ')}`);
5 }
6 let wispRememberThisAgain = createMemoryWispWithRest();
7 wispRememberThisAgain('important password', '123456789');
8 // => *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 any function which will then act as a constructor. The super keyword is new in ES6 and lets you access methods in parent classes 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 chapter you learned about ES6 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.