Update to function in ES6- Part 1
Functions are one of the primary data structures in JavaScript;
Arrow functions
ES6 introduces a new kind of function called the arrow function. Arrow functions are very similar to regular functions in behavior, but are quite different syntactically. The following code takes a list of names and converts each one to uppercase using a regular function:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) {
return name.toUpperCase();
});
The code below does the same thing except instead of passing a regular function to the map() method, it passes an arrow function. Notice the arrow in the arrow function ( => ) in the code below:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
name => name.toUpperCase()
);
Regular functions can be either function declarations or function expressions, however arrow functions are always expressions. In fact, their full name is "arrow function expressions", so they can only be used where an expression is valid. This includes being:
- stored in a variable,
- passed as an argument to a function,
- and stored in an object's property.
const greet = name => `Hello ${name}!`;
greet('Asser');
Returns: Hello Asser!
// empty parameter list requires parentheses
const sayHi = () => console.log('Hello Udacity Student!');
sayHi();
Prints: Hello Udacity Student!
// multiple parameters requires parentheses
const orderIceCream = (flavor, cone) => console.log(`Here's your ${flavor} ice cream in a ${cone} cone.`);
orderIceCream('chocolate', 'waffle');
Prints: Here's your chocolate ice cream in a waffle cone.
setTimeout(() => {
console.log('starting the test');
test.start();
}, 2000);
//Alternatively, some developers choose to use an underscore as their single parameter. The underscore never gets used, so it's undefined inside the function, but it's a common technique.
setTimeout(_ => {
console.log('starting the test');
test.start();
}, 2000);
const vowels = 'aeiou'.split('');
const bigVowels = vowels.map( (letter) => letter.toUpperCase() );
const vowels = 'aeiou'.split('');
const bigVowels = vowels.map( letter=> letter.toUpperCase() );
Concise and block body syntax All of the arrow functions we've been looking at have only had a single expression as the function body:
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
name => name.toUpperCase()
);
This format of the function body is called the "concise body syntax". The concise syntax:
- has no curly braces surrounding the function body
- and automatically returns the expression.
If you need more than just a single line of code in your arrow function's body, then you can use the "block body syntax".
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => {
name = name.toUpperCase();
return `${name} has ${name.length} characters in their name`;
});
Important things to keep in mind with the block syntax:
- it uses curly braces to wrap the function body
- and a return statement needs to be used to actually return something from the function.
const colors = ['red', 'blue', 'green', 'yellow', 'orange', 'black'];
const crazyColors = colors.map( color => {
const jumble = color.split('').reverse();
return jumble.join('') + '!';
});
const colors = ['red', 'blue', 'green', 'yellow', 'orange', 'black'];
const crazyColors = colors.map( color => color.split('').reverse().join('') + '!' );
So arrow functions are awesome!
The syntax is a lot shorter, it's easier to write and read short, single-line functions, and they automatically return when using the concise body syntax!
this keyword With regular functions the value of "this" depends on how the function is called. While with arrow functions, it depends on where that function is located in the code or on the function's surrounding context. The value of the this keyword is based completely on how its function (or method) is called. this could be any of the following:
1. A new object If the function is called with new:
const mySundae = new Sundae('Chocolate', ['Sprinkles', 'Hot Fudge']);
In the code above, the value of this inside the Sundae constructor function is a new object because it was called with new.
2. A specified object If the function is invoked with call/apply:
const result = obj1.printName.call(obj2);
In the code above, the value of this inside printName() will refer to obj2 since the first parameter of call() is to explicitly set what this refers to.
3. A context object If the function is a method of an object:
data.teleport();
In the code above, the value of this inside teleport() will refer to data.
- The global object or undefined
If the function is called with no context:
In the code above, the value of this inside teleport() is either the global object or, if in strict mode, it's undefined.[teleport();]
In other words, the value of this inside an arrow function is the same as the value of this outside the function.Let's check out an example with this in regular functions and then look at how arrow functions will work.
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = function() {
setTimeout(function() {
this.scoops++;
console.log('scoop added!');
}, 500);
};
const dessert = new IceCream();
dessert.addScoop();
Prints:
scoop added!
After running the code above, you'd think that dessert.scoops would be 1 after half a millisecond. But, unfortunately, it's not:
console.log(dessert.scoops);
Prints:
0
Can you tell why?
The function passed to setTimeout() is called without new, without call(), without apply(), and without a context object. That means the value of this inside the function is the global object and NOT the dessert object. So what actually happened was that a new scoops variable was created (with a default value of undefined) and was then incremented (undefined + 1 results in NaN):
console.log(scoops);
Prints:
NaN
One way around this is to use closure:
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = function() {
const cone = this; // sets `this` to the `cone` variable
setTimeout(function() {
cone.scoops++; // references the `cone` variable
console.log('scoop added!');
}, 0.5);
};
const dessert = new IceCream();
dessert.addScoop();
The code above will work because instead of using this inside the function, it sets the cone variable to this and then looks up the cone variable when the function is called. This works because it's using the value of the this outside the function. So if we check the number of scoops in our dessert right now, we'll see the correct value of 1:
console.log(dessert.scoops);
Prints:
1
Well that's exactly what arrow functions do, so let's replace the function passed to setTimeout() with an arrow function:
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = function() {
setTimeout(() => { // an arrow function is passed to setTimeout
this.scoops++;
console.log('scoop added!');
}, 0.5);
};
const dessert = new IceCream();
dessert.addScoop();
Since arrow functions inherit their this value from the surrounding context, this code works!
console.log(dessert.scoops);
Prints:
1
When addScoop() is called, the value of this inside addScoop() refers to dessert. Since an arrow function is passed to setTimeout(), it's using its surrounding context to determine what this refers to inside itself. So since this outside of the arrow function refers to dessert, the value of this inside the arrow function will also refer to dessert. Now what do you think would happen if we changed the addScoop() method to an arrow function?
// constructor
function IceCream() {
this.scoops = 0;
}
// adds scoop to ice cream
IceCream.prototype.addScoop = () => { // addScoop is now an arrow function
setTimeout(() => {
this.scoops++;
console.log('scoop added!');
}, 0.5);
};
const dessert = new IceCream();
dessert.addScoop();
Yeah, this doesn't work for the same reason - arrow functions inherit their this value from their surrounding context. Outside of the addScoop() method, the value of this is the global object. So if addScoop() is an arrow function, the value of this inside addScoop() is the global object. Which then makes the value of this in the function passed to setTimeout() also set to the global object!