Бонус для отличников: продвинутые темы JS
Это необязательный урок — для тех, кому интересно, что ещё прячется в глубинах JavaScript. Здесь нет задач на запоминание. Здесь — открытые двери в темы, которые вы будете изучать по мере роста. Загляните — может, что-то зацепит.
🔁 Генераторы и итераторы (function*)
Обычная функция выполняется от начала до конца и возвращает одно значение. Генератор — особенный зверь: он может приостановиться в середине выполнения и вернуть значение, сохранив своё состояние. Потом его можно «разбудить» и он продолжит с того же места.
Генераторы используют ключевое слово function* (со звёздочкой) и специальное выражение yield — «отдать значение и подождать»:
// Генератор — функция, которая может паузиться
function* counter() {
yield 1; // Остановиться и вернуть 1
yield 2; // Остановиться и вернуть 2
yield 3; // Остановиться и вернуть 3
// Конец — возвращает {value: undefined, done: true}
}
const gen = counter(); // Создаём генератор (не запускаем!)
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Генераторы работают с for...of
for (const num of counter()) {
console.log(num); // 1, 2, 3
}
Зачем это нужно? Генераторы отлично подходят для создания бесконечных последовательностей без переполнения памяти, пагинации данных, и они лежат в основе async/await (под капотом это генераторы + промисы):
// Бесконечный генератор — не съедает память
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
💡 Генераторы реализуют протокол итератора: объект с методом next(), который возвращает {value, done}. Поэтому генераторы работают с for...of, spread-оператором и деструктуризацией.
🗑️ WeakMap и WeakSet — «слабые» коллекции
Обычный Map и Set держат сильные ссылки на объекты — пока коллекция жива, объект не может быть удалён сборщиком мусора, даже если больше нигде не используется. WeakMap и WeakSet держат слабые ссылки — объект может быть удалён, когда на него нет других ссылок.
// WeakMap: ключи — только объекты, слабые ссылки
const weakMap = new WeakMap();
let user = { name: "Алиса" };
weakMap.set(user, { lastSeen: new Date() });
console.log(weakMap.get(user)); // { lastSeen: ... }
// Когда user становится null, объект удалится из памяти
// вместе со своей записью в WeakMap
user = null;
// Через некоторое время: объект { name: "Алиса" } удалён из памяти
// WeakSet: набор объектов, слабые ссылки
const visited = new WeakSet();
let page = { url: "/about" };
visited.add(page);
console.log(visited.has(page)); // true
page = null; // Объект будет удалён из памяти и из WeakSet
Когда использовать: когда нужно хранить дополнительные данные об объекте (кеш, метаданные), не мешая его жизненному циклу. Классический пример — кеширование результатов вычислений для DOM-элементов: когда элемент удаляется из DOM, кеш автоматически очистится.
⚠️ Ограничения: WeakMap нельзя итерировать (нет forEach, keys()), нельзя узнать размер. Только get, set, has, delete. Это намеренно — сборщик мусора должен работать без предупреждений.
🏷️ Symbol — уникальные идентификаторы
Symbol — примитивный тип данных (как number или string), но каждый Symbol гарантированно уникален. Никакие два Symbol не равны друг другу — даже если у них одинаковое описание.
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false! Всегда уникальны
// Символы не видны при обычном переборе объекта
const user = {
name: "Боб",
[Symbol("secret")]: "это скрыто",
};
console.log(Object.keys(user)); // ["name"] — Symbol не виден
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(secret)]
Глобальные символы через Symbol.for() — создаются один раз и переиспользуются по ключу:
const global1 = Symbol.for("app.user");
const global2 = Symbol.for("app.user");
console.log(global1 === global2); // true! Один и тот же символ
// Встроенные символы позволяют кастомизировать поведение объектов
class SpecialArray {
[Symbol.iterator]() {
// Определяем, как работает for...of для нашего класса
let index = 0;
const data = [10, 20, 30];
return {
next() {
return index < data.length
? { value: data[index++], done: false }
: { done: true };
}
};
}
}
for (const item of new SpecialArray()) {
console.log(item); // 10, 20, 30
}
💡 Встроенные Symbol (well-known symbols) — Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance — позволяют кастомизировать базовое поведение объектов в JavaScript. Это то, как React и другие библиотеки «подключаются» к языку.
🪞 Proxy и Reflect — перехватчики операций
Proxy позволяет создать объект-«посредника», который перехватывает и переопределяет операции над другим объектом: чтение, запись, удаление свойств, вызов как функции и многое другое.
const target = {
name: "Оригинал",
age: 25
};
const handler = {
// Перехватываем чтение свойства
get(obj, prop) {
console.log(`Читаем: ${prop}`);
return prop in obj ? obj[prop] : `Свойство "${prop}" не найдено`;
},
// Перехватываем запись свойства
set(obj, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Возраст должен быть числом");
}
obj[prop] = value;
return true; // Обязательно вернуть true
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Читаем: name → "Оригинал"
console.log(proxy.email); // Читаем: email → "Свойство email не найдено"
proxy.age = 30; // OK
// proxy.age = "тридцать"; // TypeError!
Reflect — объект со статическими методами, зеркально соответствующими ловушкам Proxy. Используется внутри обработчиков Proxy для «проброса» стандартного поведения:
const handler = {
get(target, prop, receiver) {
console.log(`Доступ к: ${prop}`);
return Reflect.get(target, prop, receiver); // Стандартное поведение
}
};
Где применяется: Vue 3 использует Proxy для реактивности (отслеживание изменений данных). Proxy идеален для валидации, логирования, мемоизации, создания «observable» объектов.
🔢 Typed Arrays — массивы с типами
Обычный массив JavaScript хранит значения любых типов и может менять размер. Typed Arrays — специальные массивы фиксированного размера, где каждый элемент занимает строго определённое количество байт памяти.
// Разные типы — разный размер элемента в байтах
const int8 = new Int8Array(4); // 4 байта, числа -128..127
const uint8 = new Uint8Array(4); // 4 байта, числа 0..255
const int16 = new Int16Array(4); // 8 байт, числа -32768..32767
const int32 = new Int32Array(4); // 16 байт
const float32 = new Float32Array(4); // 16 байт, числа с плавающей точкой
const float64 = new Float64Array(4); // 32 байта, двойная точность
// Работа как с обычным массивом, но строго типизированно
const pixels = new Uint8Array(4); // RGBA: 4 байта на пиксель
pixels[0] = 255; // R
pixels[1] = 128; // G
pixels[2] = 0; // B
pixels[3] = 255; // A (прозрачность)
// ArrayBuffer — сырой блок памяти
const buffer = new ArrayBuffer(16); // 16 байт сырой памяти
const view = new Int32Array(buffer); // Смотрим на них как на int32
Когда нужны: работа с бинарными файлами (изображения, аудио, видео), WebGL (3D графика), WebSockets с бинарными данными, высокопроизводительные вычисления. В обычных приложениях — редко нужны.
⏱️ Performance API — точное измерение скорости
Date.now() — грубый инструмент. Performance API позволяет измерять производительность с точностью до микросекунд и делать именованные метки:
// performance.now() — высокоточный таймер (в миллисекундах, дробные)
const start = performance.now();
// Тяжёлые вычисления
let sum = 0;
for (let i = 0; i < 10_000_000; i++) {
sum += Math.sqrt(i);
}
const end = performance.now();
console.log(`Время: ${(end - start).toFixed(3)} мс`); // Например: 45.123 мс
// Именованные метки — удобнее для сложного кода
performance.mark("start-parse");
const data = JSON.parse(largeJsonString);
performance.mark("end-parse");
performance.mark("start-sort");
data.sort((a, b) => a.value - b.value);
performance.mark("end-sort");
// Измерить между метками
performance.measure("parsing", "start-parse", "end-parse");
performance.measure("sorting", "start-sort", "end-sort");
const measures = performance.getEntriesByType("measure");
measures.forEach(m => {
console.log(`${m.name}: ${m.duration.toFixed(2)} мс`);
});
💡 Правило профилирования: не оптимизируйте код «на глаз» — сначала измерьте, где реально тратится время. Часто оказывается, что «медленное» место — вовсе не то, о котором думали.
🚪 Двери, которые ещё открыты
Это был бонусный урок, но JS прячет ещё много всего. Вот темы, которые вы встретите в дальнейшем изучении:
Многопоточность в браузере. Тяжёлые вычисления в фоновом потоке.
Запуск C/C++/Rust кода в браузере со скоростью близкой к нативной.
Новый стандарт работы с датами (заменит Date). Правильно работает с часовыми поясами.
Аннотации для классов и методов. Широко используются в NestJS и Angular.
Чистые функции, неизменяемость, каррирование, монады. Другой способ думать о коде.
Observer, Singleton, Factory, Strategy — паттерны проектирования для структурирования кода.
✅ Бонусный урок: итоги
- Генераторы (function*) — функции, которые могут паузиться и продолжать выполнение
- WeakMap/WeakSet — слабые ссылки, не мешают сборке мусора
- Symbol — гарантированно уникальные идентификаторы
- Proxy/Reflect — перехват операций над объектами (используется в Vue 3)
- Typed Arrays — массивы фиксированного типа для работы с бинарными данными
- Performance API — точное измерение времени выполнения кода
Вы завершили весь курс JavaScript. Это ваш финиш — и одновременно ваш настоящий старт. Удачи в коде! 🚀
Тест: Продвинутые темы JavaScript
6 вопросов