JavaScript · Syntax · Intermediate

Arrow Functions & this

10 tasks

Master arrow function syntax and understand how `this` behaves differently in arrow functions vs regular functions — a critical distinction for classes, callbacks, and event handlers.

Arrow function syntax and how this works

#
Arrow functions are a compact alternative to regular function expressions. The difference in syntax is simple; the difference in `this` behavior is fundamental. **Syntax** ```javascript // Regular function expression const double = function(x) { return x * 2; }; // Arrow function — same thing const double = (x) => { return x * 2; }; // Single parameter — parentheses optional const double = x => { return x * 2; }; // Single expression — braces and return optional (implicit return) const double = x => x * 2; // No parameters — parentheses required const greet = () => "Hello!"; // Returning an object literal — wrap in parentheses to avoid ambiguity with block const makeUser = name => ({ name: name, active: true }); ``` **How `this` works in arrow functions** A regular function gets its own `this` — determined by how it is called. An arrow function has no `this` of its own. It captures `this` from the surrounding lexical scope at the time the arrow function is defined, and it never changes. ```javascript class Timer { constructor() { this.seconds = 0; } start() { // Arrow function: this === the Timer instance (lexical this) setInterval(() => { this.seconds++; console.log(this.seconds); }, 1000); } startBroken() { // Regular function: this === undefined (strict) or global object setInterval(function() { this.seconds++; // TypeError or NaN — wrong this }, 1000); } } ``` **When NOT to use arrow functions** Arrow functions are not always the right tool: - Object methods: `this` in an arrow method refers to the outer scope, not the object. - Constructors: arrow functions cannot be called with `new` — they throw a TypeError. - `arguments` object: arrow functions do not have their own `arguments`. - Dynamic `this` (event handlers where you need `this` to be the element): use regular functions. ```javascript const obj = { value: 42, getValue: () => this.value, // wrong — this is not obj getValueOk() { return this.value; }, // correct }; ```

Arrow functions in array methods, classes, and promises

#
Arrow functions shine in three specific contexts: array methods, class methods with async callbacks, and promise chains. Each case exploits the lexical `this` or the concise syntax. **Array methods — the most common use** ```javascript const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10] const evens = numbers.filter(n => n % 2 === 0); // [2, 4] const total = numbers.reduce((sum, n) => sum + n, 0); // 15 // Chaining const result = numbers .filter(n => n > 2) .map(n => n * 10); // [30, 40, 50] ``` The implicit return makes single-expression callbacks compact without losing clarity. **Class methods with setTimeout / setInterval** ```javascript class Counter { constructor() { this.count = 0; } startCounting() { setInterval(() => { this.count++; console.log(this.count); }, 1000); } } ``` The arrow function inside `setInterval` captures `this` from `startCounting`'s scope — which is the class instance. A regular function would lose `this` when called by the timer. **Promise chains** ```javascript fetch("/api/user") .then(res => res.json()) .then(data => { console.log(data.name); return data; }) .catch(err => console.error(err)); ``` Arrow functions keep the chain readable. Each `.then` callback is a one-liner or a short block — no function keyword clutter. **Returning an object from an implicit-return arrow** ```javascript const makePoint = (x, y) => ({ x, y }); makePoint(3, 5); // { x: 3, y: 5 } ``` Wrap the object in parentheses — otherwise the `{` is parsed as a block start, not an object literal, and the function returns `undefined`.

Arrow functions vs regular functions — reference

#
Side-by-side comparison and quick syntax lookup. **Syntax variants** ```javascript // Multi-parameter, block body, explicit return const add = (a, b) => { return a + b; }; // Multi-parameter, implicit return const add = (a, b) => a + b; // Single parameter, implicit return const double = x => x * 2; // No parameters const rand = () => Math.random(); // Return object literal (wrap in parentheses) const point = (x, y) => ({ x, y }); ``` **Arrow vs regular function — key differences** `this`: Arrow — lexical (inherits from enclosing scope). Regular — dynamic (determined by call site). `new`: Arrow — not allowed (throws TypeError). Regular — allowed (creates instance). `arguments`: Arrow — no own `arguments` (inherits from enclosing function). Regular — has own `arguments` object. `prototype`: Arrow — no `prototype` property. Regular — has `prototype`. `super`: Arrow — inherits from enclosing method. Regular — each method has own `super`. **When to use each** Use arrow when: - Callbacks to array methods (map, filter, reduce) - Callbacks that need to preserve `this` from the outer scope (setTimeout, setInterval, promises) - Short inline expressions where `function` adds noise Use regular function when: - Object methods (you need `this` to be the object) - Constructors (called with `new`) - Functions that use `arguments` - Event handlers where `this` should be the target element
01

Rewrite as an arrow function

#

Rewrite the function `greet` as an arrow function assigned to a `const`. The behavior should be identical.

function greet(name) {
    return "Hello, " + name + "!";
}

console.log(greet("Alice"));
Solution
const greet = name => "Hello, " + name + "!";

console.log(greet("Alice"));
02

Implicit return — shorten to one line

#

Shorten `multiply` to a single-line arrow function using implicit return.

const multiply = (a, b) => {
    return a * b;
};

console.log(multiply(3, 4));
Solution
const multiply = (a, b) => a * b;

console.log(multiply(3, 4));
03

this in an object method — why arrow breaks it

#

The method `describe` uses an arrow function and returns the wrong result. Fix it so `this` refers to the object correctly.

const user = {
    name: "Alice",
    age: 30,
    describe: () => {
        return `${this.name} is ${this.age} years old`;
    }
};

console.log(user.describe());
Solution
const user = {
    name: "Alice",
    age: 30,
    describe() {
        return `${this.name} is ${this.age} years old`;
    }
};

console.log(user.describe());
04

Fix this in a setTimeout callback

#

The `DelayedGreeter` class should print a greeting after 0ms, but `this.name` is undefined. Fix it using an arrow function.

class DelayedGreeter {
    constructor(name) {
        this.name = name;
    }

    greet() {
        setTimeout(function() {
            console.log("Hello, " + this.name + "!");
        }, 0);
    }
}

new DelayedGreeter("Alice").greet();
Solution
class DelayedGreeter {
    constructor(name) {
        this.name = name;
    }

    greet() {
        setTimeout(() => {
            console.log("Hello, " + this.name + "!");
        }, 0);
    }
}

new DelayedGreeter("Alice").greet();
05

Arrow functions in map and filter

#

From the `products` array, filter only items with `price > 100` and then map each to just its `name`. Use arrow functions for both. Print the result.

const products = [
    { name: "Laptop", price: 999 },
    { name: "Mouse", price: 25 },
    { name: "Monitor", price: 399 },
    { name: "Keyboard", price: 75 },
];
// your code here
Solution
const products = [
    { name: "Laptop", price: 999 },
    { name: "Mouse", price: 25 },
    { name: "Monitor", price: 399 },
    { name: "Keyboard", price: 75 },
];
const result = products
    .filter(p => p.price > 100)
    .map(p => p.name);
console.log(result);
06

Return an object from an arrow function

#

Write an arrow function `makeProduct` that takes `name` and `price` and returns an object `{ name, price }` using implicit return. Test it with `"Laptop"` and `999`.

// your code here

console.log(makeProduct("Laptop", 999));
Solution
const makeProduct = (name, price) => ({ name, price });

console.log(makeProduct("Laptop", 999));
07

Class method vs arrow property

#

The class `Counter` has `increment` as a regular method and `decrement` as an arrow function property. Call both on an instance and print `count` after each. Observe which approach works correctly when destructured from the object.

class Counter {
    count = 0;

    increment() {
        this.count++;
    }

    decrement = () => {
        this.count--;
    };
}

const c = new Counter();
c.increment();
console.log(c.count);
c.decrement();
console.log(c.count);

// Destructure and call — which one breaks?
const { increment, decrement } = c;
increment();
console.log(c.count);
decrement();
console.log(c.count);
Solution
class Counter {
    count = 0;

    increment() {
        this.count++;
    }

    decrement = () => {
        this.count--;
    };
}

const c = new Counter();
c.increment();
console.log(c.count);
c.decrement();
console.log(c.count);

const { increment, decrement } = c;
try { increment(); } catch(e) { console.log("increment failed:", e.message); }
console.log(c.count);
decrement();
console.log(c.count);
08

Currying with nested arrow functions

#

Write a curried `add` function using arrow functions: `add(a)(b)` should return `a + b`. Test with `add(3)(4)` and `add(10)(20)`.

// your code here

console.log(add(3)(4));
console.log(add(10)(20));
Solution
const add = a => b => a + b;

console.log(add(3)(4));
console.log(add(10)(20));
09

Arrow functions in a promise chain

#

Complete the promise chain: resolve it with the value `42`, then double it in `.then`, then print the result in the next `.then`. Use arrow functions for both `.then` callbacks.

const p = new Promise(resolve => {
    resolve(42);
});

p
    .then(/* double the value */)
    .then(/* print the result */);
Solution
const p = new Promise(resolve => {
    resolve(42);
});

p
    .then(val => val * 2)
    .then(val => console.log(val));
10

Choose the right function type

#

The code below has two bugs: one method incorrectly uses an arrow function, and one callback incorrectly uses a regular function. Fix both.

class Playlist {
    constructor(name) {
        this.name = name;
        this.songs = [];
    }

    // Should print "Playlist: Rock"
    describe: () => {
        console.log("Playlist: " + this.name);
    }

    // Should print each song with the playlist name
    printSongs() {
        this.songs.forEach(function(song) {
            console.log(this.name + ": " + song);
        });
    }
}

const pl = new Playlist("Rock");
pl.songs = ["AC/DC", "Led Zeppelin"];
pl.describe();
pl.printSongs();
Solution
class Playlist {
    constructor(name) {
        this.name = name;
        this.songs = [];
    }

    describe() {
        console.log("Playlist: " + this.name);
    }

    printSongs() {
        this.songs.forEach(song => {
            console.log(this.name + ": " + song);
        });
    }
}

const pl = new Playlist("Rock");
pl.songs = ["AC/DC", "Led Zeppelin"];
pl.describe();
pl.printSongs();