➡️ Arrow Functions

ES6 Concise Function Syntax

What are Arrow Functions?

Arrow functions provide a shorter syntax for writing functions and handle the 'this' keyword differently than traditional functions.

📝 Basic Syntax

Traditional vs Arrow

// Traditional function
function add(a, b) {
    return a + b;
}

// Arrow function
const add = (a, b) => {
    return a + b;
};

// Concise arrow (implicit return)
const add = (a, b) => a + b;

// Single parameter (no parentheses needed)
const square = x => x * x;

// No parameters (parentheses required)
const greet = () => 'Hello!';

// Multiple statements (braces required)
const calculate = (x, y) => {
    let sum = x + y;
    let product = x * y;
    return { sum, product };
};

Syntax Variations

// No parameters
const sayHi = () => console.log('Hi');

// One parameter (parentheses optional)
const double = n => n * 2;
const triple = (n) => n * 3;  // Also valid

// Multiple parameters (parentheses required)
const add = (a, b) => a + b;
const greet = (name, age) => `${name} is ${age}`;

// Implicit return (one expression)
const square = x => x * x;
const isEven = n => n % 2 === 0;

// Explicit return (multiple statements)
const process = x => {
    let doubled = x * 2;
    let squared = doubled * doubled;
    return squared;
};

// Return object literal (wrap in parentheses)
const makePerson = (name, age) => ({ name, age });
// Without parentheses: const makePerson = (name, age) => { name, age }; // Error!

// Return array
const getPair = x => [x, x * 2];

// Multiline expression (without braces)
const longExpression = x =>
    x * 2 +
    x * 3 +
    x * 4;

🎯 The 'this' Keyword

Arrow Functions and 'this'

// Arrow functions don't have their own 'this'
// They inherit 'this' from the enclosing scope

const person = {
    name: 'John',
    
    // Traditional method
    greetTraditional: function() {
        console.log('Hello, ' + this.name);  // 'this' is person
    },
    
    // Arrow function (inherits 'this' from enclosing scope)
    greetArrow: () => {
        console.log('Hello, ' + this.name);  // 'this' is window/undefined
    },
    
    // Method with setTimeout
    delayedGreeting: function() {
        // Traditional function loses 'this'
        setTimeout(function() {
            console.log(this.name);  // undefined (this is window)
        }, 1000);
        
        // Arrow function preserves 'this'
        setTimeout(() => {
            console.log(this.name);  // 'John' (this is person)
        }, 1000);
    }
};

person.greetTraditional();  // "Hello, John"
person.greetArrow();         // "Hello, undefined"

// Old solution: save 'this'
const person = {
    name: 'John',
    greet: function() {
        const self = this;  // Save reference
        setTimeout(function() {
            console.log(self.name);  // Works but verbose
        }, 1000);
    }
};

// Modern solution: arrow function
const person = {
    name: 'John',
    greet: function() {
        setTimeout(() => {
            console.log(this.name);  // Clean and works
        }, 1000);
    }
};

When NOT to Use Arrow Functions

// Don't use as object methods (if you need 'this')
const person = {
    name: 'John',
    greet: () => {
        console.log(this.name);  // Won't work - 'this' is not person
    }
};

// Don't use as constructors
const Person = (name) => {
    this.name = name;  // Error: arrow functions cannot be constructors
};
// const john = new Person('John');  // TypeError

// Don't use when you need 'arguments'
const sum = () => {
    console.log(arguments);  // ReferenceError: arguments is not defined
};

// Use traditional function instead
function sum() {
    console.log(arguments);  // Works
}

// Or use rest parameters
const sum = (...args) => {
    console.log(args);  // Works
};

// Don't use for event handlers (if you need 'this' as element)
button.addEventListener('click', () => {
    console.log(this);  // window, not the button
});

// Use traditional function
button.addEventListener('click', function() {
    console.log(this);  // button element
});

🔧 Practical Use Cases

Array Methods

let numbers = [1, 2, 3, 4, 5];

// map
let doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]

// filter
let evens = numbers.filter(n => n % 2 === 0);
// [2, 4]

// reduce
let sum = numbers.reduce((acc, n) => acc + n, 0);
// 15

// find
let firstEven = numbers.find(n => n % 2 === 0);
// 2

// some
let hasEven = numbers.some(n => n % 2 === 0);
// true

// every
let allPositive = numbers.every(n => n > 0);
// true

// sort
let sorted = numbers.sort((a, b) => b - a);
// [5, 4, 3, 2, 1]

// Complex transformations
let users = [
    { name: 'John', age: 30 },
    { name: 'Jane', age: 25 },
    { name: 'Bob', age: 35 }
];

let names = users.map(u => u.name);
// ['John', 'Jane', 'Bob']

let adults = users.filter(u => u.age >= 18);

let totalAge = users.reduce((sum, u) => sum + u.age, 0);
// 90

let oldest = users.reduce((old, u) => u.age > old.age ? u : old);
// { name: 'Bob', age: 35 }

Callbacks and Promises

// setTimeout
setTimeout(() => console.log('Done'), 1000);

// Promise
fetch('/api/users')
    .then(response => response.json())
    .then(users => console.log(users))
    .catch(error => console.error(error));

// async/await
const fetchUsers = async () => {
    try {
        let response = await fetch('/api/users');
        let users = await response.json();
        return users;
    } catch (error) {
        console.error(error);
    }
};

// Event handlers (when you don't need 'this')
button.addEventListener('click', () => {
    console.log('Clicked');
});

// Chaining
Promise.resolve(5)
    .then(n => n * 2)
    .then(n => n + 3)
    .then(n => console.log(n));  // 13

Functional Programming

// Higher-order functions
const multiply = factor => number => number * factor;
const double = multiply(2);
const triple = multiply(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Function composition
const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);

const addOne = x => x + 1;
const square = x => x * x;
const double = x => x * 2;

const compute = compose(double, square, addOne);
console.log(compute(2));  // double(square(addOne(2))) = 18

// Pipe (left to right)
const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x);

const compute = pipe(addOne, square, double);
console.log(compute(2));  // double(square(addOne(2))) = 18

// Partial application
const greet = greeting => name => `${greeting}, ${name}!`;
const sayHello = greet('Hello');
const sayHi = greet('Hi');

console.log(sayHello('John'));  // "Hello, John!"
console.log(sayHi('Jane'));     // "Hi, Jane!"

// Currying
const add = a => b => c => a + b + c;
console.log(add(1)(2)(3));  // 6

const add1 = add(1);
const add1and2 = add1(2);
console.log(add1and2(3));  // 6

⚡ Arrow Functions Features

No 'arguments' Object

// Traditional function has 'arguments'
function sum() {
    return Array.from(arguments).reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3, 4));  // 10

// Arrow function doesn't have 'arguments'
const sum = () => {
    console.log(arguments);  // ReferenceError
};

// Use rest parameters instead
const sum = (...numbers) => {
    return numbers.reduce((a, b) => a + b, 0);
};

console.log(sum(1, 2, 3, 4));  // 10

No 'new' Keyword

// Traditional function can be constructor
function Person(name) {
    this.name = name;
}

const john = new Person('John');  // Works

// Arrow function cannot be constructor
const Person = (name) => {
    this.name = name;
};

// const john = new Person('John');  // TypeError: Person is not a constructor

// Use classes or traditional functions for constructors
class Person {
    constructor(name) {
        this.name = name;
    }
}

Lexical 'this' Binding

class Counter {
    constructor() {
        this.count = 0;
    }
    
    // Arrow function method (preserves 'this')
    increment = () => {
        this.count++;
        console.log(this.count);
    }
    
    // Works with event handlers
    start() {
        setInterval(this.increment, 1000);  // 'this' is preserved
    }
}

// Without arrow function
class Counter {
    constructor() {
        this.count = 0;
    }
    
    increment() {
        this.count++;
    }
    
    start() {
        // Need to bind 'this'
        setInterval(this.increment.bind(this), 1000);
    }
}

💡 Practical Examples

Data Transformation

let products = [
    { id: 1, name: 'Laptop', price: 1000, category: 'Electronics' },
    { id: 2, name: 'Phone', price: 500, category: 'Electronics' },
    { id: 3, name: 'Shirt', price: 30, category: 'Clothing' }
];

// Get all product names
let names = products.map(p => p.name);

// Filter expensive items
let expensive = products.filter(p => p.price > 100);

// Calculate total
let total = products.reduce((sum, p) => sum + p.price, 0);

// Group by category
let grouped = products.reduce((acc, p) => {
    if (!acc[p.category]) acc[p.category] = [];
    acc[p.category].push(p);
    return acc;
}, {});

// Chain operations
let affordableElectronics = products
    .filter(p => p.category === 'Electronics')
    .filter(p => p.price < 700)
    .map(p => p.name);

console.log(affordableElectronics);  // ['Phone']

Event Handling

class TodoList {
    constructor() {
        this.todos = [];
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        // Arrow functions preserve 'this'
        document.querySelector('#addBtn')
            .addEventListener('click', () => this.addTodo());
        
        document.querySelector('#input')
            .addEventListener('keypress', e => {
                if (e.key === 'Enter') this.addTodo();
            });
        
        document.querySelector('#clearBtn')
            .addEventListener('click', () => this.clearCompleted());
    }
    
    addTodo() {
        let input = document.querySelector('#input');
        this.todos.push({
            id: Date.now(),
            text: input.value,
            completed: false
        });
        input.value = '';
        this.render();
    }
    
    clearCompleted() {
        this.todos = this.todos.filter(t => !t.completed);
        this.render();
    }
    
    render() {
        let html = this.todos
            .map(todo => `
                
${todo.text}
`) .join(''); document.querySelector('#todoList').innerHTML = html; } }

API Integration

const API = {
    baseUrl: 'https://api.example.com',
    
    // Arrow functions can access 'this'
    get: async function(endpoint) {
        let response = await fetch(`${this.baseUrl}${endpoint}`);
        return response.json();
    },
    
    post: async function(endpoint, data) {
        let response = await fetch(`${this.baseUrl}${endpoint}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return response.json();
    }
};

// Using the API
const loadUsers = async () => {
    try {
        let users = await API.get('/users');
        
        // Transform data
        let activeUsers = users
            .filter(u => u.active)
            .map(u => ({ id: u.id, name: u.name }))
            .sort((a, b) => a.name.localeCompare(b.name));
        
        displayUsers(activeUsers);
    } catch (error) {
        console.error('Failed to load users:', error);
    }
};

const displayUsers = users => {
    let html = users
        .map(u => `
${u.name}
`) .join(''); document.querySelector('#users').innerHTML = html; };

Utility Functions

// Debounce
const debounce = (fn, delay) => {
    let timeout;
    return (...args) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => fn(...args), delay);
    };
};

// Throttle
const throttle = (fn, limit) => {
    let inThrottle;
    return (...args) => {
        if (!inThrottle) {
            fn(...args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
};

// Memoize
const memoize = fn => {
    let cache = {};
    return (...args) => {
        let key = JSON.stringify(args);
        if (key in cache) return cache[key];
        let result = fn(...args);
        cache[key] = result;
        return result;
    };
};

// Once
const once = fn => {
    let called = false;
    let result;
    return (...args) => {
        if (!called) {
            called = true;
            result = fn(...args);
        }
        return result;
    };
};

// Usage
const search = debounce(query => {
    console.log('Searching:', query);
}, 500);

const handleScroll = throttle(() => {
    console.log('Scrolled');
}, 1000);

const factorial = memoize(n => {
    return n <= 1 ? 1 : n * factorial(n - 1);
});

const initialize = once(() => {
    console.log('Initialized');
    return 'done';
});

🎯 Key Takeaways