$ sudo teach IT
МОДУЛЬ 12 · УРОК 8 · БОНУС

Бонус для отличников: продвинутые темы JS

Это необязательный урок — для тех, кому интересно, что ещё прячется в глубинах JavaScript. Здесь нет задач на запоминание. Здесь — открытые двери в темы, которые вы будете изучать по мере роста. Загляните — может, что-то зацепит.

🎓 Продвинутый уровень 🔬 Под капотом JS ⚡ Для любопытных

🔁 Генераторы и итераторы (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 прячет ещё много всего. Вот темы, которые вы встретите в дальнейшем изучении:

Web Workers

Многопоточность в браузере. Тяжёлые вычисления в фоновом потоке.

WebAssembly

Запуск C/C++/Rust кода в браузере со скоростью близкой к нативной.

Temporal API

Новый стандарт работы с датами (заменит Date). Правильно работает с часовыми поясами.

Decorators

Аннотации для классов и методов. Широко используются в NestJS и Angular.

Functional Programming

Чистые функции, неизменяемость, каррирование, монады. Другой способ думать о коде.

Design Patterns

Observer, Singleton, Factory, Strategy — паттерны проектирования для структурирования кода.

✅ Бонусный урок: итоги

  • Генераторы (function*) — функции, которые могут паузиться и продолжать выполнение
  • WeakMap/WeakSet — слабые ссылки, не мешают сборке мусора
  • Symbol — гарантированно уникальные идентификаторы
  • Proxy/Reflect — перехват операций над объектами (используется в Vue 3)
  • Typed Arrays — массивы фиксированного типа для работы с бинарными данными
  • Performance API — точное измерение времени выполнения кода

Вы завершили весь курс JavaScript. Это ваш финиш — и одновременно ваш настоящий старт. Удачи в коде! 🚀

Тест: Продвинутые темы JavaScript

6 вопросов

Генератор чисел

Реализуйте функцию-генератор simpleCounter(), которая возвращает три значения: 1, 2, 3 через yield. Используйте синтаксис function*.

Использование Symbol

Создайте объект user со свойством name и одним Symbol-ключом id (значение 123). Используйте Symbol('id') для ключа. Проверьте, что Symbol-свойство не видно через Object.keys.