ES6 of the Week
This Week's Episode:
arrow functions
"This"
- Remember that, within a function,
this is defined based on the
execution context of a function
- If a function is executed as a method on an object, this will refer to the object itself, so that you can intuitively access other properties/methods on the object
What if I told you...
// say we have an object called Neo...
const Neo = {};
// and we also have a function
const bendSpoon = function () {
console.log(this.spoon);
};
// let's give our object a property and a method
Neo.spoon = 'spoon';
Neo.bendSpoon = bendSpoon;
// the execution context of bendSpoon is Neo, so this refers to Neo
Neo.bendSpoon(); // spoon
// the execution context of bendSpoon is the global context - there is no spoon
bendSpoon(); // undefined
Mr. Anderson...
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = function () {
agentSmiths.forEach(function (agent) {
this.kick(agent);
});
};
Neo.kickAgentSmiths(); // TypeError: this.kick is not a function
// How can we help Neo?
Arrow functions
- A more concise way to write function expressions
-
Reminder:
- var fn = function () {} === function expression
- function fn () {} === function declaration
-
Reminder:
- The big difference: the body of arrow functions do not get their own dynamic this value. Instead, arrow functions have lexical this: their this context is the enclosing context
- This solves some issues you may have experienced with callback functions in methods like Array.prototype.forEach
- However, with great power comes great responsibility....
Syntax
/* if you have one argument and don't use brackets
you can just write a snappy one-liner
This is called the 'concise body'
*/
arr.map(i => i * 2);
// multiple arguments go in parentheses
arr.reduce((prev, next) => prev + next);
// no arguments are just an empty ()
// note that you can use this for any function expression, not just callbacks!
let foo = () => console.log('bar');
/* if you need more than one line, then you need to use brackets
and can't omit the return statement
This is called the 'block body'
*/
let baz = (x) => {
if (x > 1) return x;
else return 1;
}
Using Arrow Functions
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = function () {
agentSmiths.forEach(agent => this.kick(agent));
};
Neo.kickAgentSmiths();
/*
Neo kicked Smith1
Neo kicked Smith2
Neo kicked Smith3
*/
Arrow Combos
/*
Arrow functions love promise chains and array methods
*/
// Greatest http request of all time
$http.get('/api/artists')
.then(res => res.data)
.then(artists => artists.filter(artist => artist.name !== 'Kanye')
.catch(err => console.error);
Don't overdo it
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = () => {
agentSmiths.forEach(agent => this.kick(agent));
};
Neo.kickAgentSmiths(); // TypeError: the Matrix has you
Other Arrow Function Facts
- Call and apply can only be used to pass arguments into a function expression defined using an arrow function - there's no affect on 'this'
-
There is no arguments object exposed by arrow functions - if you try to use arguments, it will just try to find an 'arguments' variable in the enclosing scope
- You can, however, use a rest parameter...which you'll lean next week!
- If you want to use concise body to return an object literal, you need to wrap it in parentheses - otherwise, it looks like you're trying to use block body!
/* Call and apply don't affect 'this'
*/
let sum = (a, b) => a + b;
sum.call(null, 1, 2) // 3
sum.apply(null, [1, 2]) // 3
// On that note, don't expect 'arguments' to work
let differenceA = (a, b) => arguments[0] - arguments[1]; // nope
let differenceB = (...args) => args[0] - args[1]; // yuss
differenceA(2, 1) // TypeError
differenceB(2, 1) // 1
// See what's happening here?
let returnAnObject = () => { a: 1 };
let obj = returnAnObject();
obj.a; // TypeError: Cannot read property 'a' of undefined
let returnAnObjectForReals = () => ({ a: 1 });
let obj = returnAnObjectForReals();
obj.a; // 1
More Pointers
Why Use Arrow Functions?
- They look rad and can help turn your shorter functions into clean one-liners
- Intuitive this context eliminates the need to use self = this in some callbacks, which is better for OO style
When to be careful
- Whenever this is involved - make sure you know what kind of behavior you're getting
- For existing variadic functions that use the arguments object - you need to refactor it to use the rest parameter first
Resources
- ES6: http://es6-features.org/
- YDKJS: https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch2.md#arrow-functions
- Airbnb style guide: https://github.com/airbnb/javascript
-
Mozilla docs:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
ES6 of the Week - 2
By Tom Kelly
ES6 of the Week - 2
Arrow functions
- 1,385