JavaScript · Синтаксис · Промежуточный

Стрелочные функции и this

10 задач

Освой синтаксис стрелочных функций и пойми как `this` ведёт себя по-разному в стрелочных и обычных функциях — ключевое различие для классов, колбэков и обработчиков событий.

Синтаксис стрелочных функций и как работает this

#
Стрелочные функции — это компактная альтернатива обычным функциональным выражениям. Разница в синтаксисе простая; разница в поведении `this` — фундаментальная. **Синтаксис** ```javascript // Обычное функциональное выражение const double = function(x) { return x * 2; }; // Стрелочная функция — то же самое const double = (x) => { return x * 2; }; // Один параметр — скобки необязательны const double = x => { return x * 2; }; // Одно выражение — фигурные скобки и return необязательны (неявный возврат) const double = x => x * 2; // Без параметров — скобки обязательны const greet = () => "Hello!"; // Возврат литерала объекта — обернуть в скобки чтобы избежать неоднозначности с блоком const makeUser = name => ({ name: name, active: true }); ``` **Как `this` работает в стрелочных функциях** Обычная функция получает собственный `this` — определяется тем как она вызывается. Стрелочная функция не имеет собственного `this`. Она захватывает `this` из окружающей лексической области в момент определения стрелочной функции, и он никогда не меняется. ```javascript class Timer { constructor() { this.seconds = 0; } start() { // Стрелочная функция: this === экземпляр Timer (лексический this) setInterval(() => { this.seconds++; console.log(this.seconds); }, 1000); } startBroken() { // Обычная функция: this === undefined (strict) или глобальный объект setInterval(function() { this.seconds++; // TypeError или NaN — неправильный this }, 1000); } } ``` **Когда НЕ использовать стрелочные функции** Стрелочные функции подходят не всегда: - Методы объекта: `this` в стрелочном методе ссылается на внешнюю область, не на объект. - Конструкторы: стрелочные функции нельзя вызывать через `new` — они бросают TypeError. - Объект `arguments`: у стрелочных функций нет собственного `arguments`. - Динамический `this` (обработчики событий где `this` должен быть элементом): используй обычные функции. ```javascript const obj = { value: 42, getValue: () => this.value, // неправильно — this не является obj getValueOk() { return this.value; }, // правильно }; ```

Стрелочные функции в методах массива, классах и промисах

#
Стрелочные функции особенно удобны в трёх контекстах: методы массивов, методы классов с асинхронными колбэками и цепочки промисов. Каждый случай использует лексический `this` или лаконичный синтаксис. **Методы массивов — самое частое использование** ```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 // Цепочка const result = numbers .filter(n => n > 2) .map(n => n * 10); // [30, 40, 50] ``` Неявный возврат делает колбэки из одного выражения лаконичными без потери ясности. **Методы классов с setTimeout / setInterval** ```javascript class Counter { constructor() { this.count = 0; } startCounting() { setInterval(() => { this.count++; console.log(this.count); }, 1000); } } ``` Стрелочная функция внутри `setInterval` захватывает `this` из области `startCounting` — то есть из экземпляра класса. Обычная функция потеряла бы `this` при вызове таймером. **Цепочки промисов** ```javascript fetch("/api/user") .then(res => res.json()) .then(data => { console.log(data.name); return data; }) .catch(err => console.error(err)); ``` Стрелочные функции делают цепочку читаемой. Каждый колбэк `.then` — однострочник или короткий блок без лишнего ключевого слова `function`. **Возврат объекта с неявным return** ```javascript const makePoint = (x, y) => ({ x, y }); makePoint(3, 5); // { x: 3, y: 5 } ``` Оборачивай объект в скобки — иначе `{` воспринимается как начало блока, не объектный литерал, и функция возвращает `undefined`.

Стрелочные vs обычные функции — справочник

#
Сравнение бок о бок и быстрый справочник по синтаксису. **Варианты синтаксиса** ```javascript // Несколько параметров, блочное тело, явный возврат const add = (a, b) => { return a + b; }; // Несколько параметров, неявный возврат const add = (a, b) => a + b; // Один параметр, неявный возврат const double = x => x * 2; // Без параметров const rand = () => Math.random(); // Возврат объектного литерала (обернуть в скобки) const point = (x, y) => ({ x, y }); ``` **Стрелочная vs обычная функция — ключевые отличия** `this`: Стрелочная — лексический (наследует из окружающей области). Обычная — динамический (определяется местом вызова). `new`: Стрелочная — запрещено (бросает TypeError). Обычная — разрешено (создаёт экземпляр). `arguments`: Стрелочная — нет собственного `arguments` (наследует из охватывающей функции). Обычная — имеет собственный объект `arguments`. `prototype`: Стрелочная — нет свойства `prototype`. Обычная — есть `prototype`. `super`: Стрелочная — наследует от охватывающего метода. Обычная — каждый метод имеет собственный `super`. **Когда использовать каждую** Выбирай стрелочную когда: - Колбэки для методов массива (map, filter, reduce) - Колбэки где нужно сохранить `this` из внешней области (setTimeout, setInterval, промисы) - Короткие встроенные выражения где `function` добавляет шум Выбирай обычную функцию когда: - Методы объекта (нужен `this` как объект) - Конструкторы (вызов через `new`) - Функции которые используют `arguments` - Обработчики событий где `this` должен быть целевым элементом
01

Переписать как стрелочную функцию

#

Перепиши функцию `greet` как стрелочную функцию, присвоенную в `const`. Поведение должно быть идентичным.

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

console.log(greet("Alice"));
Решение
const greet = name => "Hello, " + name + "!";

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

Неявный возврат — сократить до одной строки

#

Сократи `multiply` до однострочной стрелочной функции с неявным возвратом.

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

console.log(multiply(3, 4));
Решение
const multiply = (a, b) => a * b;

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

this в методе объекта — почему стрелка ломает

#

Метод `describe` использует стрелочную функцию и возвращает неправильный результат. Исправь его так чтобы `this` правильно ссылался на объект.

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

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

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

Исправить this в колбэке setTimeout

#

Класс `DelayedGreeter` должен вывести приветствие через 0мс, но `this.name` является undefined. Исправь с помощью стрелочной функции.

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

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

new DelayedGreeter("Alice").greet();
Решение
class DelayedGreeter {
    constructor(name) {
        this.name = name;
    }

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

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

Стрелочные функции в map и filter

#

Из массива `products` отфильтруй только товары с `price > 100` и преобразуй каждый только в `name`. Используй стрелочные функции для обеих операций. Выведи результат.

const products = [
    { name: "Laptop", price: 999 },
    { name: "Mouse", price: 25 },
    { name: "Monitor", price: 399 },
    { name: "Keyboard", price: 75 },
];
// твой код здесь
Решение
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

Вернуть объект из стрелочной функции

#

Напиши стрелочную функцию `makeProduct`, принимающую `name` и `price` и возвращающую объект `{ name, price }` с неявным возвратом. Проверь с `"Laptop"` и `999`.

// твой код здесь

console.log(makeProduct("Laptop", 999));
Решение
const makeProduct = (name, price) => ({ name, price });

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

Метод класса vs стрелочное свойство

#

Класс `Counter` имеет `increment` как обычный метод и `decrement` как стрелочную функцию-свойство. Вызови оба на экземпляре и выведи `count` после каждого. Понаблюдай какая форма корректно работает после деструктуризации.

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;
increment();
console.log(c.count);
decrement();
console.log(c.count);
Решение
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

Каррирование через вложенные стрелочные функции

#

Напиши каррированную функцию `add` со стрелочными функциями: `add(a)(b)` должна возвращать `a + b`. Проверь через `add(3)(4)` и `add(10)(20)`.

// твой код здесь

console.log(add(3)(4));
console.log(add(10)(20));
Решение
const add = a => b => a + b;

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

Стрелочные функции в цепочке промисов

#

Завершить цепочку промисов: resolve со значением `42`, затем удвоить в `.then`, затем вывести результат в следующем `.then`. Используй стрелочные функции для обоих колбэков.

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

p
    .then(/* удвоить значение */)
    .then(/* вывести результат */);
Решение
const p = new Promise(resolve => {
    resolve(42);
});

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

Выбрать правильный тип функции

#

Код ниже имеет два бага: один метод неправильно использует стрелочную функцию, а один колбэк неправильно использует обычную функцию. Исправь оба.

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

    // Должен выводить "Playlist: Rock"
    describe: () => {
        console.log("Playlist: " + this.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();
Решение
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();