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
  • 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
Made with Slides.com