Docs
README
5.3 Arrow Functions
Overview
Arrow functions, introduced in ES6 (ES2015), provide a more concise syntax for writing function expressions. They also have important differences in how they handle the this keyword, making them ideal for certain use cases like callbacks and methods that need to preserve the surrounding context.
Table of Contents
- ā¢Basic Syntax
- ā¢Syntax Variations
- ā¢Implicit Returns
- ā¢Arrow Functions vs Regular Functions
- ā¢The
thisKeyword - ā¢No
argumentsObject - ā¢Cannot Be Used as Constructors
- ā¢Best Use Cases
- ā¢When NOT to Use Arrow Functions
- ā¢Common Patterns
- ā¢Best Practices
Basic Syntax
Traditional vs Arrow
// Traditional function expression
const greet = function (name) {
return 'Hello, ' + name;
};
// Arrow function
const greetArrow = (name) => {
return 'Hello, ' + name;
};
Anatomy of Arrow Function
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Arrow Function Anatomy ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā const add = (a, b) => { return a + b; }; ā
ā āāāāāā āā āāāāāāāāāāāāā ā
ā params arrow body ā
ā ā
ā const add = (a, b) => a + b; ā
ā āāāāāā āāāāā ā
ā params implicit return ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Syntax Variations
No Parameters
const sayHello = () => 'Hello!';
const sayHi = () => {
console.log('Hi!');
};
Single Parameter (parentheses optional)
// With parentheses
const double = (x) => x * 2;
// Without parentheses
const triple = (x) => x * 3;
Multiple Parameters (parentheses required)
const add = (a, b) => a + b;
const sum = (a, b, c) => a + b + c;
With Block Body
const calculate = (a, b) => {
const sum = a + b;
const product = a * b;
return { sum, product };
};
Default Parameters
const greet = (name = 'World') => `Hello, ${name}!`;
greet(); // "Hello, World!"
greet('Alice'); // "Hello, Alice!"
Rest Parameters
const sumAll = (...numbers) => {
return numbers.reduce((acc, n) => acc + n, 0);
};
sumAll(1, 2, 3, 4); // 10
Implicit Returns
Arrow functions can return values without the return keyword when using a concise body.
Expression Body (Implicit Return)
// Implicit return of expression
const double = (x) => x * 2;
const add = (a, b) => a + b;
const isEven = (n) => n % 2 === 0;
Block Body (Explicit Return Required)
// Block body requires explicit return
const calculate = (a, b) => {
const result = a * b;
return result; // Must use return
};
Returning Objects (Wrap in Parentheses)
// ā Syntax error - looks like block body
const makeUser = (name) => {
name: name;
};
// ā
Wrap object in parentheses
const makeUser = (name) => ({ name: name });
// ā
Or use block body
const makeUser = (name) => {
return { name: name };
};
Multi-line Expressions
// Wrap in parentheses for multi-line expressions
const createPerson = (name, age) => ({
name,
age,
isAdult: age >= 18,
});
Arrow Functions vs Regular Functions
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Arrow vs Regular Function Comparison ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Feature Arrow Regular ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā Syntax Concise Verbose ā
ā this binding Lexical Dynamic ā
ā arguments object No Yes ā
ā Constructor (new) No Yes ā
ā Hoisting No Declarations: Yes ā
ā Methods Not ideal Preferred ā
ā Callbacks Preferred Works ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
| Feature | Arrow Function | Regular Function |
|---|---|---|
this | Lexical (inherited) | Dynamic (caller) |
arguments | Not available | Available |
Can use new | No | Yes |
Has prototype | No | Yes |
| Suitable as method | No | Yes |
| Best for callbacks | Yes | Sometimes |
The this Keyword
The most important difference: arrow functions don't have their own this. They inherit this from the enclosing scope.
Regular Function - Dynamic this
const obj = {
name: 'Object',
regularMethod: function () {
console.log('Regular this:', this.name); // "Object"
setTimeout(function () {
console.log('Callback this:', this.name); // undefined (or window)
}, 100);
},
};
Arrow Function - Lexical this
const obj = {
name: 'Object',
arrowMethod: function () {
console.log('Method this:', this.name); // "Object"
setTimeout(() => {
console.log('Arrow this:', this.name); // "Object" (preserved!)
}, 100);
},
};
Classic Workaround vs Arrow Function
// Old workaround with 'self' or 'that'
const obj1 = {
name: 'Old Way',
method: function () {
const self = this; // Save reference
setTimeout(function () {
console.log(self.name);
}, 100);
},
};
// Modern approach with arrow function
const obj2 = {
name: 'Arrow Way',
method: function () {
setTimeout(() => {
console.log(this.name); // Just works!
}, 100);
},
};
No arguments Object
Arrow functions don't have their own arguments object.
// Regular function has arguments
function regularFunc() {
console.log(arguments); // [1, 2, 3]
}
regularFunc(1, 2, 3);
// Arrow function doesn't
const arrowFunc = () => {
// console.log(arguments); // ReferenceError!
};
// Use rest parameters instead
const arrowWithRest = (...args) => {
console.log(args); // [1, 2, 3]
};
arrowWithRest(1, 2, 3);
Accessing Outer arguments
function outer() {
const inner = () => {
console.log(arguments); // outer's arguments
};
inner();
}
outer(1, 2, 3); // Logs [1, 2, 3]
Cannot Be Used as Constructors
Arrow functions cannot be used with the new keyword.
// Regular function - can be constructor
function Person(name) {
this.name = name;
}
const p1 = new Person('Alice'); // Works
// Arrow function - cannot be constructor
const PersonArrow = (name) => {
this.name = name;
};
// const p2 = new PersonArrow("Bob"); // TypeError!
No prototype Property
function Regular() {}
console.log(Regular.prototype); // {}
const Arrow = () => {};
console.log(Arrow.prototype); // undefined
Best Use Cases
1. Array Methods
const numbers = [1, 2, 3, 4, 5];
// Map
const doubled = numbers.map((n) => n * 2);
// Filter
const evens = numbers.filter((n) => n % 2 === 0);
// Reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);
// Find
const firstBig = numbers.find((n) => n > 3);
// Every/Some
const allPositive = numbers.every((n) => n > 0);
const hasEven = numbers.some((n) => n % 2 === 0);
// Sort
const sorted = [...numbers].sort((a, b) => a - b);
2. Callbacks with Preserved this
class Counter {
constructor() {
this.count = 0;
}
start() {
// Arrow preserves 'this'
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000);
}
}
3. Promise Chains
fetch('/api/data')
.then((response) => response.json())
.then((data) => data.items)
.then((items) => items.filter((item) => item.active))
.catch((error) => console.error(error));
4. Functional Programming
const compose =
(...fns) =>
(x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const pipe =
(...fns) =>
(x) =>
fns.reduce((acc, fn) => fn(acc), x);
const addOne = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;
const transform = pipe(addOne, double, square);
console.log(transform(2)); // 36
5. Short Event Handlers
button.addEventListener('click', () => console.log('Clicked!'));
input.addEventListener('input', (e) => updateValue(e.target.value));
When NOT to Use Arrow Functions
1. Object Methods
// ā Arrow function - 'this' doesn't refer to object
const person = {
name: 'Alice',
greet: () => {
console.log(this.name); // undefined
},
};
// ā
Regular function or method shorthand
const person = {
name: 'Alice',
greet() {
console.log(this.name); // "Alice"
},
};
2. Constructors
// ā Cannot use new with arrow functions
const Person = (name) => {
this.name = name;
};
// ā
Use regular function or class
function Person(name) {
this.name = name;
}
3. Dynamic Context Required
// ā Arrow function loses dynamic context
button.addEventListener('click', () => {
console.log(this); // Not the button!
});
// ā
Regular function for dynamic 'this'
button.addEventListener('click', function () {
console.log(this); // The button element
});
4. Prototype Methods
// ā Arrow function on prototype
Person.prototype.greet = () => {
console.log(this.name); // Wrong 'this'
};
// ā
Regular function
Person.prototype.greet = function () {
console.log(this.name); // Correct
};
Common Patterns
1. Chaining Array Methods
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 20 },
];
const result = users
.filter((user) => user.age >= 21)
.map((user) => user.name)
.sort((a, b) => a.localeCompare(b));
// ["Alice", "Bob"]
2. Currying
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. Partial Application
const greet = (greeting) => (name) => `${greeting}, ${name}!`;
const sayHello = greet('Hello');
const sayHi = greet('Hi');
console.log(sayHello('Alice')); // "Hello, Alice!"
console.log(sayHi('Bob')); // "Hi, Bob!"
4. Immediate Callback Return
// Get property
const getName = (obj) => obj.name;
// Extract field
const users = [{ name: 'A' }, { name: 'B' }];
const names = users.map((u) => u.name);
// Boolean check
const isValid = (item) => item != null && item.valid;
5. Conditional Return
const abs = (n) => (n >= 0 ? n : -n);
const max = (a, b) => (a > b ? a : b);
const clamp = (n, min, max) => (n < min ? min : n > max ? max : n);
Best Practices
1. Use for Short Callbacks
// ā
Clean and readable
numbers.map((n) => n * 2);
numbers.filter((n) => n > 0);
// ā Too verbose
numbers.map(function (n) {
return n * 2;
});
2. Parentheses Style
// Style 1: Always use parentheses
const add = (a, b) => a + b;
const double = (x) => x * 2;
// Style 2: Omit for single parameter
const double = (x) => x * 2;
const greet = (name, greeting) => `${greeting}, ${name}`;
// Be consistent within your codebase!
3. Explicit Return for Complex Logic
// ā Hard to read
const process = (data) =>
data
.filter((x) => x.active)
.map((x) => x.value)
.reduce((a, b) => a + b, 0);
// ā
Use block body for clarity
const process = (data) => {
const active = data.filter((x) => x.active);
const values = active.map((x) => x.value);
return values.reduce((a, b) => a + b, 0);
};
4. Don't Nest Too Deeply
// ā Hard to follow
const result = arr.map((x) =>
x.items.filter((i) => i.active).map((i) => i.value)
);
// ā
Extract functions
const getActiveValues = (items) =>
items.filter((i) => i.active).map((i) => i.value);
const result = arr.map((x) => getActiveValues(x.items));
Summary
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Arrow Functions Quick Reference ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā // Syntax variations ā
ā () => expression // No params ā
ā x => expression // One param ā
ā (x, y) => expression // Multiple params ā
ā (x, y) => { return value; } // Block body ā
ā x => ({ key: value }) // Return object ā
ā ā
ā Key characteristics: ā
ā ⢠Concise syntax ā
ā ⢠Lexical 'this' binding ā
ā ⢠No 'arguments' object ā
ā ⢠Cannot be used with 'new' ā
ā ⢠No 'prototype' property ā
ā ā
ā Best for: ā
ā ⢠Array methods (map, filter, reduce) ā
ā ⢠Callbacks with 'this' preservation ā
ā ⢠Promise chains ā
ā ⢠Short functions ā
ā ā
ā Avoid for: ā
ā ⢠Object methods ā
ā ⢠Constructors ā
ā ⢠Dynamic 'this' required ā
ā ⢠Prototype methods ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Next Steps
- ā¢Practice with the examples in
examples.js - ā¢Complete the exercises in
exercises.js - ā¢Learn about parameters and arguments in depth
- ā¢Explore closures and function binding