Jan Hesters

Arrow Functions

An arrow function is like a normal function just with 4 key differences:

  1. They have a different syntax (duh).
  2. There is no form of arrow function declaration, only expressions.
  3. They don't get their own this.
  4. They have an implicit return.

1. Different syntax

Here is the syntax of an arrow function compared to a regular function keyword. I use Immediately Invoked Function Expression for both functions.

(function() {
  console.log('function keyword');
})();
 
(() => {
  console.log('arrow function');
})();

Notice that if an arrow function takes a single argument without any fanciness like destructuring, you may omit the brackets around its parameters.

No arguments: you need brackets.

const noArguments = () => {};

One argument: you can omit brackets.

const oneArgument = n => {
  return n * 2;
};

Default parameters: you need brackets.

const defaultParameter = (name = 'Anon') => {
  return name;
};

Destructuring: you need brackets.

const destructuring = ({ name }) => {
  return name;
};

More than one argument: you need brackets.

const moreThanOneArgument = (a, b) => {
  return a + b;
};

2. Only expressions

You can also name a function by writing a variable name after the function keyword. This is called "function declaration". There are no function declarations for arrow functions, just anonymous function expressions.

// function declaration with keyword
function decleration() {
  console.log('function declaration with keyword');
}
 
// function expression with keyword
const keywordExpression = function() {
  console.log('function expression with keyword');
};
 
// function declaration with arrow function
// 🔴 Not a thing.
 
// function expression with arrow function
const arrowExpression = () => {
  console.log('function expression with keyword');
};

The difference between a function declaration and a function expression is that they are parsed at different times. The declaration is defined everywhere in its scope, whereas the expression is only defined when its line is reached.

declaration(); // ✅ Okay
 
function decleration() {}
 
foo(); // 🔴 TypeError: foo is not a function
var foo = () => {};
// We could've also written var foo = function() {}
bar(); // 🔴 ReferenceError: Cannot access 'bar' before initialization
const bar = () => {};

You can also see the difference between const and var here. Since foo was declared using the var keyword, it is hoisted, and its value is undefined. foo() tries to call undefined, but its obviously not a function. Since const doesn't hoist invoking bar throws a reference error.

3. No this

Like other languages, JavaScript has a this keyword. There are several ways this is bound explicitly or implicitly. We are only going to focus on the behavior relevant to arrow functions.

const car = {
  wheels: 'four',
  yellWheels() {
    return this.wheels.toUpperCase();
  },
  countWheels: () => {
    return this.wheels;
  },
};
 
car.yellWheels(); // 'FOUR'
car.countWheels(); // undefined

Using the function keyword, this references the object. However, the arrow function doesn't get its own this. Therefore wheels is undefined because the global object doesn't have a property wheels.

To understand this, play Eric Elliott's "What is this?".

4. Implicit return

In the previous code snippet, we used the return keyword to return values from the functions. Nevertheless, arrow functions don't need to do that. If your function body is a single expression, you can omit the curly braces and the expression gets returned automatically.

// These two functions do the same
const explicitReturn = () => {
  return 'foo';
};
const implicitReturn = () => 'foo';
 
explicitReturn(); // 'foo'
implicitReturn(); // 'foo'

Using implicit returns, we can simplify the examples from the syntax section.

// Can't be simplified, no expression
const noop = () => {};
 
// Can be simplified, return expressions
const getName = (name = 'Anon') => name;
const double = n => n * 2;
const getNameProp = ({ name }) => name;
const add = (a, b) => a + b;

Implicit returns become especially handy for currying, which is when a function returns another function until it returns its final value.

const add = a => b => a + b; // add is a function that returns b => a + b
// Read this as const add = a => (b => a + b);
const inc = add(1); // inc is a function because b => a + b got returned
const decr = add(-1);
 
inc(3); // 4 because inc remembers a as 1
inc(6); // 7
decr(3); // 2 because decr remembers a as -1

add is a function that takes in a and returns a function that takes in b that returns the sum of a and b. The function that takes in b remembers a in its closure.

Learn senior fullstack secrets

Subscribe to my newsletter for weekly updates on new videos, articles, and courses. You'll also get exclusive bonus content and discounts.