Добавить в корзинуПозвонить
Найти в Дзене
ovnoCod

Язык JavaScript - Качество кода

Вы написали код. Он не работает. Вы добавляете console.log('тут'). Потом ещё один. Потом console.log('сюда доходит?'). Через час вы окружены лесоповалом из сообщений в консоли, а баг всё ещё здесь. Я знаю эту боль. Мы все через это проходили. Но есть путь джедая. Инструменты отладки в браузере - это не магия для избранных. Это мощный арсенал, который превращает поиск ошибок из гадания на кофейной гуще в детективное расследование с лупой, отпечатками пальцев и замедленной съёмкой. Вы знаете console.log. Но есть целая семья: javascript // Базовые
console.log("Обычное сообщение");
console.info("Информация");
console.warn("Предупреждение");
console.error("Ошибка");
// Группировка
console.group("Пользователи");
console.log("Анна");
console.log("Борис");
console.groupEnd();
// Таблицы (божественно для массивов!)
const users = [
{ id: 1, name: "Анна", age: 25 },
{ id: 2, name: "Борис", age: 30 },
{ id: 3, name: "Вика", age: 28 }
];
console.table(users);
// Стилизация (да, консоль мож
Оглавление
картинка взята с ya.ru
картинка взята с ya.ru

Охотники за багами: Искусство отладки JavaScript в браузере (или как перестать ставить console.log)

Вы написали код. Он не работает. Вы добавляете console.log('тут'). Потом ещё один. Потом console.log('сюда доходит?'). Через час вы окружены лесоповалом из сообщений в консоли, а баг всё ещё здесь.

Я знаю эту боль. Мы все через это проходили.

Но есть путь джедая. Инструменты отладки в браузере - это не магия для избранных. Это мощный арсенал, который превращает поиск ошибок из гадания на кофейной гуще в детективное расследование с лупой, отпечатками пальцев и замедленной съёмкой.

Часть 1. Консоль - Ваш первый друг (и главный враг)

1.1 Консольное братство

Вы знаете console.log. Но есть целая семья:

javascript

// Базовые
console.log("Обычное сообщение");
console.info("Информация");
console.warn("Предупреждение");
console.error("Ошибка");

// Группировка
console.group("Пользователи");
console.log("Анна");
console.log("Борис");
console.groupEnd();

// Таблицы (божественно для массивов!)
const users = [
{ id: 1, name: "Анна", age: 25 },
{ id: 2, name: "Борис", age: 30 },
{ id: 3, name: "Вика", age: 28 }
];
console.table(users);

// Стилизация (да, консоль можно красить)
console.log("%c Красный жирный текст", "color: red; font-weight: bold");

// Замер времени
console.time("api");
await fetch("/api/data");
console.timeEnd("api");
// "api: 234ms"

// Счетчик
console.count("клик");
// клик: 1
console.count("клик");
// клик: 2

// Трассировка стека
function one() { two(); }
function two() { three(); }
function three() { console.trace("Путь вызова"); }
one();

1.2 console.log против console.dir

javascript

const element = document.querySelector("button");
console.log(element);
// HTML-представление
console.dir(element);
// Объектное представление со всеми свойствами

1.3 Ловушка с объектами в console.log

javascript

const user = { name: "Анна" };
console.log(user);
// { name: "Анна" }
user.name = "Борис";
console.log(user);
// { name: "Борис" } (НО!)

// В некоторых браузерах первый лог покажет "Борис"
// Потому что объект отображается при открытии консоли, а не в момент лога

// Решение:
console.log(JSON.parse(JSON.stringify(user)));
console.log({ ...user });

Часть 2. Breakpoints - Остановись, мгновение!

debugger - это магическое слово. Когда движок доходит до него, выполнение останавливается.

javascript

function calculatePrice(price, discount) {
debugger;
// Здесь код заморозится
const result = price * (1 - discount);
return result;
}

Но ставить debugger в коде - прошлый век. Современные DevTools дают больше.

2.1 Виды брейкпоинтов

1. Строчный (Line-of-code) - клик на номер строки. Самый простой.

2. Условный (Conditional) - правый клик → "Add conditional breakpoint". Останавливается только когда условие истинно.

javascript

// Остановится только когда i === 100
for (let i = 0; i < 1000; i++) {
process(i);
}

3. DOM-брейкпоинт - останавливается при изменении элемента.

javascript

// В DevTools: Elements → правый клик → Break on → attribute modification
// Когда кто-то меняет стиль или атрибут, вы узнаете кто

4. XHR/fetch брейкпоинт - при сетевых запросах к определённому URL.

5. Брейкпоинт на событии - при клике, наведении, загрузке страницы.

6. Исключения (Exception) - кнопка ⏸ в Sources → Pause on exceptions.

2.2 Что делать на паузе?

Когда код заморожен, вы можете:

  • Смотреть переменные - наводите мышью на любую переменную
  • Выполнять код в консоли - консоль теперь в контексте этой строки
  • Смотреть стек вызовов (Call Stack) - кто вызвал эту функцию
  • Смотреть область видимости (Scope) - какие переменные доступны
  • Смотреть Watch - следить за конкретными выражениями

javascript

// Watch-выражения:
user.name
price * discount
users.filter(u => u.age > 18).length

Часть 3. Пошаговая отладка - Контроль времени

Когда код на паузе, вы управляете временем:

-2

Практический пример:

javascript

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

function multiply(a, b) {
return a * b;
}

function calculate(x, y) {
const sum = add(x, y);
// ⬅️ брейкпоинт здесь
const product = multiply(x, y);
return product - sum;
}

  • Step over - выполнит add (не заходя внутрь) и остановится на следующей строке
  • Step into - зайдёт внутрь функции add
  • Step out - закончит текущую функцию и вернётся в calculate

Часть 4. Blackboxing - Игнорируйте чужой код

Вы когда-нибудь нажимали "Step into" и оказывались в дебрях jQuery или React? Это бесит.

Решение: Blackboxing. В DevTools → Settings → Ignore List. Добавьте скрипты, которые не хотите отлаживать.

javascript

// Теперь Step into пропустит эти библиотеки
*/node_modules
/*
*/jquery.js
*/react-dom
/*

Часть 5. Conditional Breakpoints - Хирургия багов

Допустим, массив из 1000 элементов, и баг возникает на 777-м. Ставить брейкпоинт в цикле и нажимать "Resume" 777 раз? Нет.

javascript

// Правый клик на номере строки → Add conditional breakpoint
for (let i = 0; i < users.length; i++) {
processUser(users[i]);
// Брейкпоинт с условием: i === 777
}

Условием может быть любое выражение:

javascript

user.age > 100
user.name.includes("ё")
index % 100 === 0
// каждый 100-й
JSON.stringify(user).includes("секрет")

Часть 6. Logpoints - console.log без мусора в коде

Не хотите захламлять код console.log, но нужно смотреть значение переменной на каждой итерации?

Logpoint - брейкпоинт, который не останавливает код, а просто выводит сообщение.

javascript

// Правый клик → Add logpoint
// Сообщение: `Пользователь ${user.name}, индекс: ${index}`
for (let i = 0; i < users.length; i++) {
const user = users[i];
processUser(user);
}

Преимущества:

  • Не меняет код
  • Можно выключить одним кликом
  • Можно логировать сложные выражения

Часть 7. Watch и Scope - Рентген ваших переменных

Scope (Область видимости)

Показывает все доступные переменные в текущем контексте:

  • Local - локальные переменные функции
  • Closure - переменные из замыканий
  • Global - глобальные переменные (window)

Watch (Наблюдение)

Добавьте любое выражение, и оно будет пересчитываться на каждом шаге.

javascript

// Примеры полезных watch-выражений:
users.filter(u => u.active).length
total / count
JSON.stringify(currentState)
document.activeElement

Часть 8. Отладка асинхронного кода - Поймать неуловимый баг

Асинхронный код - это ад для отладки. Вы ставите брейкпоинт, а выполнение идёт своим путём.

Async Stack Traces

Современные DevTools умеют показывать полную цепочку асинхронных вызовов:

javascript

async function fetchUser() {
const response = await fetch("/api/user");
const data = await response.json();
return data;
}

async function displayUser() {
const user = await fetchUser();
// Брейкпоинт здесь
console.log(user.name);
}

displayUser();

В стеке вызовов вы увидите и displayUser, и fetchUser, несмотря на await.

Отладка Promise

javascript

Promise.resolve()
.then(() => console.log(1))
.then(() => console.log(2))
.catch(err => console.error(err));

Ставьте брейкпоинты внутри then или используйте debugger.

Отладка setTimeout/setInterval

javascript

setTimeout(() => {
debugger;
// Остановится через 1 секунду
console.log("Timeout");
}, 1000);

Часть 9. Инструменты профилирования - Кто тормозит?

Performance Tab

Ваша страница тормозит? Performance панель покажет, где именно.

  1. Нажмите ● (Record)
  2. Выполните действие, которое тормозит
  3. Остановите запись

Что смотреть:

  • Красные полоски - долгие задачи (Long Tasks), блокирующие UI
  • Стрелки вверх-вниз - принудительный reflow/repaint
  • Цветные блоки - время на JS (жёлтый), рендеринг (фиолетовый), покраска (зелёный)

Memory Tab

Утечка памяти? Memory панель поможет:

  1. Heap snapshot - снимок памяти в моменте
  2. Allocation instrumentation - запись выделений памяти в реальном времени
  3. Allocation sampling - семплирование выделений

Признаки утечки:

  • После действий память не уменьшается
  • Объектов в памяти становится всё больше
  • Detached DOM nodes (узлы, удалённые из DOM, но оставшиеся в памяти)

Часть 10. Отладка React/Vue/Angular

React DevTools

bash

# Установите расширение React DevTools

Что можно:

  • Смотреть props и state компонента
  • Изменять значения на лету
  • Профилировать рендеры (⚡ Highlight updates)
  • Искать компоненты по дереву

javascript

// В консоли DevTools
$r
// ссылка на выбранный компонент
$r.props
$r.state
$r.forceUpdate()

Vue DevTools

Аналогично, но с поддержкой Vuex, Pinia и событий.

Angular DevTools

Профилирование изменений, дерево компонентов.

Часть 11. Реальные сценарии отладки

Сценарий #1: "Ничего не происходит при клике"

План:

  1. Открыть Sources → Event Listener Breakpoints → Mouse → click
  2. Нажать на элемент
  3. Код остановится на первом обработчике
  4. Пройти Step over/into, пока не найдёте свою функцию
  5. Проверить, что условие срабатывает (if (isActive))

Сценарий #2: "Значение меняется непонятно кем"

План:

  1. Правый клик на строке с переменной → "Break on value change" (не везде)
  2. Или найти все места изменения:javascript// Добавьте временно в код
    Object.defineProperty(window, 'problemVar', {
    set(value) {
    debugger;
    this._value = value;
    },
    get() { return this._value; }
    });

Сценарий #3: "Бесконечный цикл"

План:

  1. Включить Pause on exceptions
  2. Нажать F8 (Resume)
  3. Браузер покажет, где зациклилось (обычно через 5-10 секунд)

Сценарий #4: "Не загружается скрипт"

План:

  1. Network Tab → посмотреть статус (404? 500?)
  2. Sources → нажать Ctrl+P → искать файл
  3. Если не находится → скрипт не загрузился

Часть 12. Горячие клавиши (шпаргалка)

-3

Часть 13. Психология отладки (самое важное)

Правило 1: Останавливайтесь и думайте

80% времени уходит на гадание, 20% - на реальную отладку. Переверните.

Плохо: "А если я поменяю тут... ой, а теперь вот тут... хм, не работает... может, сюда добавить console.log?"

Хорошо: "Эта переменная должна быть массивом, но она null. Где она обнуляется? Посмотрю стек вызовов и проверю все места изменения."

Правило 2: Воспроизведите проблему

Если баг не воспроизводится стабильно, вы не можете его отладить.

Плохо: "Иногда кнопка не работает, перезагрузи страницу и попробуй снова"

Хорошо: "Чтобы воспроизвести: войти под пользователем X, нажать три раза на кнопку, потом быстро кликнуть в правый верхний угол"

Правило 3: Минимальный пример

Создайте изолированный пример, исключив всё лишнее.

javascript

// Вместо 500 строк кода
function reproduce() {
const a = 0.1;
const b = 0.2;
console.log(a + b);
// 0.30000000000000004
}

Правило 4: Прочитайте ошибку

Да, полностью. В ней есть название файла и номер строки.

javascript

// TypeError: Cannot read property 'name' of undefined
// at processUser (app.js:42)
// at renderUsers (app.js:15)

Вы знаете: в app.js строка 42, переменная user - undefined.

Правило 5: Используйте поиск

В DevTools: Ctrl+Shift+F по всем файлам.

Ищете, откуда вызывается setUser? Найдите setUser( или setUser =.

Итог: Манифест отладчика

  1. Перестаньте гадать - используйте брейкпоинты.
  2. Перестаньте засорять код - используйте logpoints.
  3. Перестаньте жать "Step into" 100 раз - используйте conditional breakpoints.
  4. Перестаньте теряться в асинхронности - используйте async stack traces.
  5. Перестаньте гадать, где память - используйте Memory Tab.

Ваш чек-лист перед сдачей кода:

  • Все console.log удалены (или оставлены осознанно)
  • Все debugger удалены
  • Вы прошлись по основным сценариям с брейкпоинтами
  • Проверили вкладку Console (нет ошибок или предупреждений)
  • Проверили вкладку Network (все запросы успешны)
  • Посмотрели Performance (нет долгих задач)

Последний совет: отладка - это навык. Он не даётся с первой строчки кода. Он приходит с опытом, с разочарованиями, с ночными сессиями, когда баг сдаётся на пятый час. Но когда вы научитесь - вы станете неуязвимы. Любой баг, любая ошибка, любое "у меня не работает" станет просто загадкой с решением.

Открывайте DevTools, ставьте брейкпоинты и ловите этих багов. Они не спрячутся.

------------------------------------------------------------------------

------------------------------------------------------------------------

Эстетика кода: Почему ваш JavaScript должен быть красивым (и как это спасёт вашу карьеру)

Код пишется один раз, а читается сотни раз. Вашими коллегами. Вашим начальником. Вами самими через полгода, когда вы забудете, что здесь вообще происходит.

Стиль кода - это не про "нравится/не нравится". Это про коммуникацию. Чистый код - это вежливость. Грязный код - это записка на салфетке, которую вы оставляете коллегам в 2 часа ночи.

Давайте разберёмся, как писать JavaScript, который хочется читать.

Часть 1. Форматирование - Дышите, код, дышите!

1.1 Отступы (пробелы vs табы)

Вечный спор. Главное - не смешивать.

javascript

// Плохо (смесь пробелов и табов)
function bad() {
→→const a = 1;
· const b = 2;
}

// Хорошо (только пробелы - 2 или 4)
function good() {
const a = 1;
const b = 2;
}

Консенсус индустрии: 2 пробела (Airbnb, Google) или 4 (StandardJS). Выберите одно и придерживайтесь.

1.2 Точки с запятой

Это религия. JS вставляет их автоматически (ASI), но иногда ошибается.

javascript

// Без точки с запятой - рискованно
const a = 1
const b = 2
(function() {})()
// Ошибка! JS думает, что 2(function)

// С точкой с запятой - безопасно
const a = 1;
const b = 2;
(function() {})();

// Моя рекомендация: всегда ставьте ;

1.3 Длина строки

80-100 символов. Никто не хочет скроллить горизонтально.

javascript

// Плохо (140 символов в строке)
const result = someVeryLongFunctionName(parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8);

// Хорошо (разбито)
const result = someVeryLongFunctionName(
parameter1,
parameter2,
parameter3,
parameter4,
parameter5,
parameter6,
parameter7,
parameter8
);

1.4 Пустые строки - воздух для кода

Группируйте логические блоки.

javascript

// Плохо (стена текста)
function processUser(user) {
if (!user) return null;
const name = user.name.trim();
const email = user.email.toLowerCase();
const isValid = name && email;
if (!isValid) return null;
const result = { name, email };
return result;
}

// Хорошо (разделено на смысловые блоки)
function processUser(user) {
if (!user) return null;

const name = user.name.trim();
const email = user.email.toLowerCase();
const isValid = name && email;

if (!isValid) return null;

const result = { name, email };
return result;
}

Часть 2. Именование - Как назвать корабль

2.1 Переменные - существительные

javascript

// Плохо (непонятно, что хранит)
const d = 5;
const data = fetch();
const temp = user.name;

// Хорошо (ясно, что это)
const daysLeft = 5;
const userProfile = fetch();
const userName = user.name;

2.2 Функции - глаголы

javascript

// Плохо
function user() {}
function data() {}

// Хорошо
function getUser() {}
function fetchData() {}
function validateEmail() {}
function calculateTotal() {}

2.3 Булевы значения - вопросы

javascript

// Плохо
let active = true;
let loading = false;

// Хорошо (читается как вопрос)
let isActive = true;
let isLoading = false;
let hasPermission = true;
let canEdit = false;
let shouldUpdate = true;

2.4 Константы - ВЕРХНИЙ_РЕГИСТР

javascript

// Плохо
const maxCount = 100;
const apiKey = "secret";

// Хорошо
const MAX_COUNT = 100;
const API_KEY = "secret";
const DEFAULT_TIMEOUT = 5000;

2.5 Массивы - множественное число

javascript

// Плохо
const user = ['Анна', 'Борис'];
const userList = ['Анна', 'Борис'];

// Хорошо
const users = ['Анна', 'Борис'];
const productNames = ['Книга', 'Ручка'];

2.6 Избегайте венгерской нотации

javascript

// Плохо (венгерская нотация - тип в имени)
const sName = "Анна";
const iCount = 10;
const bIsActive = true;
const aUsers = [];

// Хорошо
const name = "Анна";
const count = 10;
const isActive = true;
const users = [];

2.7 Магические числа - выносите в константы

javascript

// Плохо
if (status === 404) {}
setTimeout(fn, 5000);

// Хорошо
const HTTP_NOT_FOUND = 404;
const REFRESH_INTERVAL_MS = 5000;

if (status === HTTP_NOT_FOUND) {}
setTimeout(fn, REFRESH_INTERVAL_MS);

Часть 3. Комментарии - Говорите, когда нужно молчать

3.1 Хорошие комментарии объясняют ПОЧЕМУ, а не ЧТО

javascript

// Плохо (очевидно)
// Увеличиваем i на 1
i++;

// Хорошо (объясняет причину)
// Компенсируем сдвиг индекса после удаления элемента
i--;

// Плохо (шум)
// Присваиваем значение переменной name
const name = "Анна";

// Хорошо (важная информация)
// Используем часовой пояс UTC для избежания проблем с夏令时间
const timestamp = new Date(Date.UTC(2024, 0, 1));

3.2 Комментируйте сложные алгоритмы

javascript

// Реализация алгоритма быстрого возведения в степень
// Сложность: O(log n)
function fastPower(base, exponent) {
if (exponent === 0) return 1;
if (exponent % 2 === 0) {
const half = fastPower(base, exponent / 2);
return half * half;
}
return base * fastPower(base, exponent - 1);
}

3.3 TODO и FIXME - честность перед будущим

javascript

// TODO: добавить обработку ошибок
// FIXME: не работает для отрицательных чисел
// HACK: временное решение до рефакторинга бэкенда
// NOTE: зависит от версии API 2.0

3.4 JSDoc - документация для всех

javascript

/**
* Вычисляет сумму двух чисел
* @param {number} a - Первое число
* @param {number} b - Второе число
* @returns {number} Сумма a и b
* @example
* add(2, 3) // 5
*/
function add(a, b) {
return a + b;
}

Часть 4. Функции - Стройте кирпичики

4.1 Одна функция - одно действие (Single Responsibility)

javascript

// Плохо (делает три вещи)
function processUser(user) {
validateUser(user);
saveToDatabase(user);
sendEmail(user);
}

// Хорошо (разделено)
function processUser(user) {
if (!validateUser(user)) return;
saveToDatabase(user);
notifyUser(user);
}

4.2 Короткие функции (10-15 строк - идеал)

javascript

// Плохо (100 строк)
function doEverything() {
// ... 100 строк
}

// Хорошо (разбито на маленькие)
function stepOne() {}
function stepTwo() {}
function stepThree() {}
function orchestrate() {
stepOne();
stepTwo();
stepThree();
}

4.3 Максимум 3 параметра

javascript

// Плохо (много параметров)
function createUser(name, age, email, phone, address, city, country) {}

// Хорошо (объект параметров)
function createUser({ name, age, email, phone, address, city, country }) {}

// Использование
createUser({
name: "Анна",
age: 25,
email: "anna@example.com",
// ...
});

4.4 Избегайте побочных эффектов

javascript

// Плохо (меняет внешний массив)
function addToArray(arr, value) {
arr.push(value);
return arr;
}

// Хорошо (возвращает новый массив)
function addToArray(arr, value) {
return [...arr, value];
}

Часть 5. Сравнения и условия

5.1 Используйте строгое сравнение (===)

javascript

// Плохо
if (value == 5) {}
if (value != null) {}

// Хорошо
if (value === 5) {}
if (value !== null && value !== undefined) {}
// Или
if (value != null) {}
// единственное легальное использование ==

5.2 Избегайте отрицательных условий (когда можно)

javascript

// Плохо
if (!isNotActive) {}

// Хорошо
if (isActive) {}

5.3 Ранние возвраты (early return) вместо вложенных if

javascript

// Плохо (вложенность)
function getDiscount(user) {
if (user) {
if (user.isPremium) {
if (user.years > 5) {
return 0.3;
} else {
return 0.2;
}
} else {
return 0;
}
}
return 0;
}

// Хорошо (ранние возвраты)
function getDiscount(user) {
if (!user) return 0;
if (!user.isPremium) return 0;
if (user.years > 5) return 0.3;
return 0.2;
}

5.4 Тернарный оператор - только для простого

javascript

// Хорошо (простое присваивание)
const status = age >= 18 ? 'adult' : 'child';

// Плохо (сложная логика)
const result = a > b ? a > c ? a : c : b > c ? b : c;

// Хорошо (if для сложного)
let result;
if (a > b && a > c) result = a;
else if (b > c) result = b;
else result = c;

Часть 6. Структуры данных

6.1 Используйте деструктуризацию

javascript

// Плохо
const name = user.name;
const age = user.age;
const city = user.address.city;

// Хорошо
const { name, age, address: { city } } = user;

6.2 Spread вместо Object.assign

javascript

// Плохо
const newObj = Object.assign({}, obj1, obj2);

// Хорошо
const newObj = { ...obj1, ...obj2 };

6.3 Используйте map/filter/reduce вместо циклов

javascript

// Плохо
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}

// Хорошо
const doubled = numbers.map(n => n * 2);

Часть 7. Асинхронность

7.1 async/await вместо then/catch

javascript

// Плохо
fetch('/api/user')
.then(res => res.json())
.then(data => processUser(data))
.catch(err => console.error(err));

// Хорошо
try {
const res = await fetch('/api/user');
const data = await res.json();
processUser(data);
} catch (err) {
console.error(err);
}

7.2 Обрабатывайте ошибки

javascript

// Плохо
const data = await fetch('/api/data');
// что если ошибка?

// Хорошо
try {
const data = await fetch('/api/data');
return data;
} catch (error) {
console.error('Failed to fetch data:', error);
return null;
}

Часть 8. Ошибки и граничные случаи

8.1 Всегда проверяйте входные данные

javascript

// Плохо
function divide(a, b) {
return a / b;
// что если b === 0?
}

// Хорошо
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}

8.2 Используйте guard-клаузы для undefined/null

javascript

// Плохо
function getUserName(user) {
if (user && user.name) {
return user.name;
}
return 'Guest';
}

// Хорошо (опциональная цепочка + nullish coalescing)
function getUserName(user) {
return user?.name ?? 'Guest';
}

Часть 9. Форматирование строк

9.1 Используйте шаблонные строки

javascript

// Плохо
const message = 'Привет, ' + name + '! Тебе ' + age + ' лет.';

// Хорошо
const message = `Привет, ${name}! Тебе ${age} лет.`;

9.2 Длинные строки

javascript

// Плохо
const longText = "Это очень длинная строка, которая не помещается в 80 символов и её трудно читать, потому что она тянется в одну линию";

// Хорошо
const longText = "Это очень длинная строка, которая не помещается в 80 символов " +
"и её трудно читать, потому что она тянется в одну линию";

// Или с шаблонными строками
const longText = `
Это очень длинная строка, которая не помещается в 80 символов
и её трудно читать, потому что она тянется в одну линию
`;

Часть 10. Организация файлов

10.1 Порядок внутри файла

javascript

// 1. Импорты
import React from 'react';
import { useState } from 'react';

// 2. Константы
const DEFAULT_TIMEOUT = 5000;
const API_ENDPOINT = '/api/users';

// 3. Утилиты
function formatDate(date) { ... }

// 4. Основная логика
export function UserProfile({ userId }) { ... }

// 5. Экспорт по умолчанию
export default UserProfile;

10.2 Именованный экспорт vs default

javascript

// Хорошо для одного главного экспорта
export default function Button() {}

// Хорошо для нескольких утилит
export function formatDate() {}
export function parseDate() {}
export const DATE_FORMAT = 'DD.MM.YYYY';

Часть 11. Инструменты - Ваши верные помощники

11.1 Prettier - автоматическое форматирование

bash

npm install --save-dev prettier

json

// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}

11.2 ESLint - ловит ошибки до выполнения

bash

npm install --save-dev eslint

json

// .eslintrc
{
"extends": ["eslint:recommended", "airbnb-base"],
"rules": {
"no-console": "warn",
"no-unused-vars": "error"
}
}

11.3 EditorConfig - единый стиль в команде

ini

# .editorconfig
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

Часть 12. Антипаттерны (чего делать НЕ НАДО)

12.1 Не меняйте сигнатуру встроенных объектов

javascript

// НИКОГДА ТАК НЕ ДЕЛАЙТЕ
Array.prototype.shuffle = function() { ... }
Object.prototype.superMethod = function() { ... }

12.2 Не используйте with

javascript

// Плохо (запрещено в строгом режиме)
with (obj) {
console.log(a, b, c);
}

12.3 Не используйте eval

javascript

// Плохо (опасно и медленно)
eval('const result = ' + userInput + ';');

// Хорошо (без eval)
const result = JSON.parse(userInput);

12.4 Не оставляйте debugger в продакшене

javascript

// Плохо
function process() {
debugger;
// забыли удалить
return result;
}

Часть 13. Чек-лист перед коммитом

  • Переменные названы осмысленно (не x, data, temp)
  • Функции делают одну вещь и названы глаголом
  • Нет магических чисел (вынесены в константы)
  • Используется === вместо ==
  • Нет вложенных if глубже 3 уровней
  • Функции короче 30 строк (лучше 15-20)
  • Комментарии объясняют "почему", а не "что"
  • Нет console.log в продакшен-коде
  • Код отформатирован (Prettier)
  • ESLint показывает 0 ошибок

Итог: Манифест стиля

  1. Код читают люди - пишите для человека, который будет поддерживать ваш код (это может быть вы через полгода).
  2. Будьте последовательны - любой стиль лучше, чем смесь стилей.
  3. Автоматизируйте - Prettier + ESLint решают 90% проблем.
  4. Имена имеют значение - хорошее имя заменяет комментарий.
  5. Короткие функции - легче тестировать, отлаживать, понимать.
  6. Ранние возвраты - убивают вложенность.
  7. Не будьте умным - понятный код лучше "гениального".

И последнее: самый лучший стиль - это тот, которого придерживается вся команда. Договоритесь, запишите в README, автоматизируйте проверку. И тогда ваш код будет не просто работать - он будет радовать глаз.

Помните: вы пишете код не для компьютера. Компьютер исполнит любой, даже самый ужасный код. Вы пишете для людей. Сделайте им приятно.

--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

Молчаливые стражи: Искусство комментариев в JavaScript (или почему ваш код должен рассказывать истории)

Вы когда-нибудь возвращались к своему коду через три месяца и не понимали, зачем вы написали эту странную строчку? Или тратили час, пытаясь разобраться в чужой функции, где единственный комментарий - это // TODO fix?

Комментарии - это письма в будущее. Сегодня вы пишете их для себя через полгода. Или для коллеги, который будет дежурить в пятницу вечером. Хороший комментарий спасает часы дебага. Плохой - создаёт иллюзию понимания.

Давайте разберёмся, как комментировать так, чтобы вас благословляли, а не проклинали.

Часть 1. Синтаксис: Два пути сказать что-то важное

1.1 Однострочные комментарии //

javascript

// Это однострочный комментарий
const answer = 42;
// Можно ставить после кода

// Часто используют для отключения кода
// const oldVersion = calculateOldWay();
const newVersion = calculateNewWay();

1.2 Многострочные комментарии /* */

javascript

/*
Это многострочный комментарий.
Он может занимать несколько строк.
Идеален для длинных объяснений.
*/

function complexAlgorithm() {
/* Иногда используют внутри строки,
но лучше так не делать */ const x = 5;
}

1.3 HTML-комментарии в JS? (нет)

javascript

<!-- Так писать нельзя, это ошибка -->
// В JS работают только // и /* */

Часть 2. Почему комментарии важны (и когда опасны)

2.1 Хорошие комментарии спасают жизни

javascript

// Кейс: компенсация сдвига индекса
function removeItem(arr, index) {
// Удаляем элемент
arr.splice(index, 1);
// Компенсируем, так как массив изменился
index--;
return arr;
}

2.2 Плохие комментарии - это шум

javascript

// Плохо (очевидно)
let x = 5;
// Присваиваем x значение 5

// Плохо (устаревший)
// Проверяем, является ли пользователь админом
if (user.role === 'admin') {
// теперь тут проверка на модератора
if (user.role === 'moderator') {
grantAccess();
}
}

2.3 Лучший комментарий - это самодокументируемый код

javascript

// Плохо (нужен комментарий)
// Проверяем, не истек ли срок действия
if (Date.now() > expiryDate) {}

// Хорошо (код говорит сам за себя)
const isExpired = Date.now() > expiryDate;
if (isExpired) {}

// Плохо
// Валидация email
if (email.includes('@') && email.includes('.')) {}

// Хорошо
const isValidEmail = email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (isValidEmail(email)) {}

Часть 3. Что комментировать: Фокус на "ПОЧЕМУ"

3.1 Комментируйте бизнес-логику

javascript

// ПОЧЕМУ? (не что)
// Скидка 20% для пользователей, зарегистрированных более года
// Это требование отдела маркетинга (тикет #12345)
const discount = user.registeredDays > 365 ? 0.2 : 0;

// ПОЧЕМУ? (не что)
// Используем UTC, чтобы избежать проблем с часовыми поясами
// Клиенты из Новосибирска и Калининграда видят одинаковые даты
const today = new Date().toISOString().split('T')[0];

3.2 Комментируйте сложные алгоритмы

javascript

// Реализация алгоритма Левенштейна для поиска опечаток
// Расстояние = минимальное количество правок для превращения одной строки в другую
function levenshteinDistance(a, b) {
const matrix = [];

// Инициализация матрицы: первая строка и столбец — индексы
for (let i = 0; i <= b.length; i++) matrix[i] = [i];
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;

// Заполняем матрицу, сравнивая символы
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
matrix[i][j] = Math.min(
matrix[i - 1][j] + 1,
// удаление
matrix[i][j - 1] + 1,
// вставка
matrix[i - 1][j - 1] + cost
// замена
);
}
}

return matrix[b.length][a.length];
}

3.3 Комментируйте "грабли" и обходные пути

javascript

// HACK: В старых версиях Safari есть баг с flexbox
// Пришлось добавить position: relative, иначе ломается вёрстка
.element {
position: relative;
}

// FIXME: Временное решение до обновления API бэкенда
// Ожидаем, что поле user.full_name появится в версии 2.0
const userName = user.full_name || `${user.first_name} ${user.last_name}`;

// TODO: Оптимизировать производительность (O(n^2) для больших массивов)
function findDuplicates(arr) { ... }

3.4 Комментируйте побочные эффекты

javascript

// ВНИМАНИЕ: Эта функция изменяет исходный массив
function sortInPlace(arr) {
return arr.sort();
}

// НЕ ИСПОЛЬЗОВАТЬ: Меняет глобальный объект window
function setGlobalTheme(theme) {
window.theme = theme;
}

Часть 4. JSDoc: Документация для профессионалов

JSDoc - это стандарт документирования функций, классов и модулей.

4.1 Базовый JSDoc

javascript

/**
* Складывает два числа
* @param {number} a - Первое слагаемое
* @param {number} b - Второе слагаемое
* @returns {number} Сумма a и b
*/
function add(a, b) {
return a + b;
}

4.2 Сложные типы

javascript

/**
* @param {string[]} names - Массив строк
* @param {Object} options - Настройки
* @param {boolean} options.isActive - Активировать обработку
* @param {number} [options.timeout=5000] - Таймаут (опционально)
* @returns {Promise<User[]>}
*/
function fetchUsers(names, options) {
// ...
}

4.3 Примеры использования

javascript

/**
* Форматирует дату в человекочитаемый вид
* @param {Date} date - Дата для форматирования
* @param {string} [locale='ru-RU'] - Локаль
* @returns {string} Отформатированная дата
* @example
* formatDate(new Date(2024, 0, 15)) // "15 января 2024 г."
* formatDate(new Date(), 'en-US') // "January 15, 2024"
*/
function formatDate(date, locale = 'ru-RU') {
return date.toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}

4.4 Типы возврата и ошибки

javascript

/**
* Находит пользователя по ID
* @param {number} id - ID пользователя
* @returns {Promise<User>} Найденный пользователь
* @throws {NotFoundError} Если пользователь не найден
* @throws {ValidationError} Если ID не является положительным числом
*/
async function getUser(id) {
if (id <= 0) throw new ValidationError('ID must be positive');
const user = await db.findUser(id);
if (!user) throw new NotFoundError(`User ${id} not found`);
return user;
}

4.5 Теги для типов (TypeScript без TypeScript)

javascript

/**
* @typedef {Object} User
* @property {number} id - Уникальный идентификатор
* @property {string} name - Имя пользователя
* @property {string} email - Email (уникальный)
* @property {boolean} isActive - Статус активности
*/

/**
* @type {User[]}
*/
const users = [];

/**
* @callback CallbackFunction
* @param {Error} error - Ошибка или null
* @param {User} user - Найденный пользователь
*/

Часть 5. TODO, FIXME, HACK, NOTE - Язык разработчиков

Эти теги понимают IDE и подсвечивают их.

5.1 TODO - Надо сделать

javascript

// TODO: Добавить валидацию email
// TODO: Вынести магические числа в константы
// TODO(anna): Оптимизировать запрос к БД (тормозит на 10к+ записей)
function processUsers(users) {
// TODO: Обработать случай пустого массива
return users.map(user => user.name);
}

5.2 FIXME - Сломано, надо чинить

javascript

// FIXME: Не работает для отрицательных чисел
// FIXME: Иногда возвращает null, хотя не должен
function calculatePercentage(part, whole) {
// FIXME: Деление на ноль!
return (part / whole) * 100;
}

5.3 HACK - Временный костыль

javascript

// HACK: Обходим баг в библиотеке moment.js (версия 2.29.0)
// https://github.com/moment/moment/issues/1234
// Удалить после обновления библиотеки
const fixedDate = moment(date).add(1, 'day');

// HACK: CSS-фикс для Safari
/* @noflip */
.selector {
-webkit-appearance: none;
}

5.4 NOTE - Важное замечание

javascript

// NOTE: Порядок аргументов важен! Сначала ширина, потом высота
function createBox(width, height) {}

// NOTE: Функция мутирует объект для производительности
function updateInPlace(obj) {}

5.5 XXX - Здесь опасно/важно

javascript

// XXX: Это место требует рефакторинга перед добавлением нового функционала
// XXX: Изменения здесь могут сломать модуль оплаты
function criticalPaymentLogic() {}

Часть 6. Антипаттерны: Как НЕ надо комментировать

6.1 Очевидные комментарии (шум)

javascript

// Плохо
let counter = 0;
// Счетчик
counter++;
// Увеличиваем счетчик
console.log(counter);
// Выводим счетчик

// Хорошо (код говорит сам за себя)
let clickCount = 0;
clickCount++;
console.log(clickCount);

6.2 Комментарии-книги

javascript

// Плохо
/***************************************************
* Функция для обработки пользовательских данных *
* Автор: Анна *
* Дата: 15.01.2024 *
* Версия: 3.2 *
* Лицензия: MIT *
***************************************************/
function processData() {}

// Хорошо (информация в системе контроля версий)
function processData() {}

6.3 Закомментированный код

javascript

// Плохо (мусор)
// const oldWay = calculateOld();
// const anotherOld = calculateAnother();
// for (let i = 0; i < 100; i++) {
// console.log(i);
// }
const newWay = calculateNew();

// Хорошо (удаляем, не храним историю в комментариях)
const newWay = calculateNew();

6.4 Комментарии-извинения

javascript

// Плохо
// Извините за этот код, я знаю, что он ужасен
// Просто времени не было, переделаю потом
function uglyFunction() {}

// Хорошо (потрать время сейчас или напиши TODO)
function readableFunction() {}

6.5 Строчные комментарии, разрывающие поток

javascript

// Плохо
const result = someLongFunction(
param1,
// первый параметр
param2,
// второй параметр
param3
// третий параметр
);

// Хорошо (имена говорят сами за себя)
const result = someLongFunction(
firstName,
lastName,
userAge
);

Часть 7. Комментарии в команде: Создаём культуру

7.1 Code Review комментарии

javascript

// В PR:
// @reviewer: Проверьте граничные условия (массив может быть пустым)
function processItems(items) {
return items.map(item => item.value);
}

7.2 Комментарии для новичков

javascript

// Добро пожаловать в модуль аутентификации!
// Основные функции: login(), logout(), refreshToken()
// Смотрите примеры в tests/auth.test.js

/**
* Вход пользователя в систему
* @param {string} email - Email (регистр не важен)
* @param {string} password - Пароль (минимум 8 символов)
* @returns {Promise<Session>}
*/
async function login(email, password) {
// ...
}

7.3 Комментарии-ссылки

javascript

// См. спецификацию: https://wiki.company.com/auth-flow
// Баг-трекер: JIRA-1234
// Решение основано на статье: https://stackoverflow.com/questions/12345
function handleAuthRedirect() {
// ...
}

Часть 8. Реальные примеры из практики

8.1 Сложный баг с часовыми поясами

javascript

/**
* Преобразует время сервера в локальное время пользователя
*
* ПОЧЕМУ: Сервер в UTC+0, пользователи по всему миру
* ПРОБЛЕМА: JavaScript Date автоматически конвертирует, но теряет информацию
* РЕШЕНИЕ: Храним timestamp, конвертируем на клиенте
*
* @example
* // Сервер вернул 2024-01-15T12:00:00Z
* const localTime = toLocalTime('2024-01-15T12:00:00Z');
* // В Новосибирске (UTC+7) будет 19:00
*/
function toLocalTime(serverTime) {
// Не используем new Date(serverTime) напрямую — там свои грабли
const timestamp = Date.parse(serverTime);
return new Date(timestamp);
}

8.2 Оптимизация производительности

javascript

// Кэшируем результат, потому что вычисление сложное
// Пересчитываем только при изменении зависимостей
let cachedResult = null;
let lastDeps = null;

function expensiveComputation(deps) {
// NOTE: Используем поверхностное сравнение для производительности
if (lastDeps === deps) return cachedResult;

lastDeps = deps;
cachedResult = doExpensiveWork(deps);
return cachedResult;
}

8.3 Обходной путь для браузерного бага

javascript

// HACK: В IE11 отсутствует Array.prototype.includes
// Удалить, когда прекратим поддержку IE
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement) {
return this.indexOf(searchElement) !== -1;
};
}

// FIXME: В Safari 14 баг с IntersectionObserver
// https://bugs.webkit.org/show_bug.cgi?id=12345
// Используем polyfill до выхода Safari 15
if (!window.IntersectionObserver) {
// загружаем polyfill
}

Часть 9. Инструменты для работы с комментариями

9.1 ESLint правила для комментариев

json

{
"rules": {
"spaced-comment": ["error", "always"],
"capitalized-comments": ["warn", "always"],
"no-warning-comments": ["warn", {
"terms": ["todo", "fixme", "xxx"],
"location": "start"
}]
}
}

9.2 Генерация документации из JSDoc

bash

# Установка
npm install -g jsdoc

# Генерация документации
jsdoc src/*.js -d docs/

9.3 Prettier и комментарии

Prettier не ломает комментарии, но форматирует их.

javascript

// До Prettier
const x = 5;
// комментарий

// После Prettier (оставляет на месте)
const x = 5;
// комментарий

Часть 10. Чек-лист хорошего комментария

  • Объясняет ПОЧЕМУ, а не ЧТО
  • Актуален (не противоречит коду)
  • Лаконичен (не больше, чем нужно)
  • Написан понятным языком (без жаргона)
  • Не дублирует очевидное
  • Содержит ссылки на баги/задачи/документацию (если нужно)
  • Использует правильные теги (TODO, FIXME, NOTE)

Часть 11. Философия комментирования

11.1 Закон "Двух уровней"

Первый уровень - для понимания кода (что делает функция, какие параметры, что возвращает).

Второй уровень - для понимания контекста (почему это здесь, почему именно так, какие ограничения).

11.2 Закон "Читатель всегда прав"

Если читателю непонятно - виноват комментарий (или код). Не оправдывайте сложность словами "ну это же очевидно".

11.3 Закон "Умирающих комментариев"

Комментарии устаревают быстрее кода. Люди забывают их обновлять. Хороший комментарий - тот, который напоминает о себе (например, ссылкой на баг-трекер, который закроют при исправлении).

Итог: Манифест комментатора

  1. Пишите код, который не нуждается в комментариях - хорошие имена, короткие функции, понятная логика.
  2. Комментируйте "почему", а не "что" - что видно из кода, почему - нет.
  3. Не храните историю в комментариях - для этого есть Git.
  4. Используйте JSDoc - это стандарт, его понимают IDE и генераторы документации.
  5. TODO, FIXME, HACK - ваши друзья - но возвращайтесь к ним.
  6. Удаляйте закомментированный код - он создаёт ложное чувство безопасности.
  7. Будьте вежливы - комментарии читают люди.

Финальный тест: какой комментарий лучше?

javascript

// Вариант 1:
// Прибавляем 1 к i
i++;

// Вариант 2:
// Компенсируем удаление элемента из массива
i--;

// Вариант 3:
i++;

Ответ: Вариант 2, потому что объясняет почему мы меняем переменную, а не просто описывает действие.

Помните: код - это то, что делает компьютер. Комментарии - то, почему он это делает. И если ваш код не может объяснить себя сам, комментарии становятся голосом разума. Используйте этот голос мудро.

--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

Тёмное искусство: Почему ниндзя-код - это проклятие, а не суперсила

В легендах о JavaScript есть таинственные существа - ниндзя-программисты. Они пишут код, который никто не может понять. Они используют однобуквенные переменные, магические числа и конструкции, от которых у линтера случается инфаркт.

Их код работает. Но только до первого бага. А когда баг приходит, его невозможно отладить. Никто не хочет трогать код ниндзя. Даже сам ниндзя через месяц.

Это история о том, как НЕ надо писать код. И о том, почему "умный" код - часто самый глупый.

Часть 1. Философия ниндзя (или как оправдать ужасный код)

Ниндзя-программист считает себя гением. Он уверен, что код должны читать только компьютеры. А люди - пусть страдают.

Принципы ниндзя:

  • Чем короче имя переменной - тем быстрее я печатаю
  • Чем больше вложенности - тем я круче
  • Чем меньше комментариев - тем я загадочнее
  • Если код сложный - значит, задача сложная

Правда: Хороший код - это код, который может понять стажёр. Плохой код - тот, который не понимает даже автор через неделю.

Часть 2. Именование во тьме (или "Поиск переменной x")

2.1 Однобуквенные переменные

Ниндзя любит загадки:

javascript

// Код ниндзя
let a = 5;
let b = 10;
let c = a + b;
let d = c * 2;
let e = d - 3;

// Что здесь происходит? Гадайте!

Правильное имя:

javascript

const price = 5;
const quantity = 10;
const subtotal = price + quantity;
const totalWithTax = subtotal * 2;
const finalPrice = totalWithTax - 3;

2.2 Аббревиатуры, понятные только ниндзя

javascript

// Код ниндзя
const usr = getUsr();
const pwd = getPwd();
const vld = validate(usr, pwd);

// Понятный код
const user = getUser();
const password = getPassword();
const isValid = validate(user, password);

Исключения: Общепринятые аббревиатуры (id, url, api, db, html, css, js).

2.3 Похожие имена (тест на внимательность)

javascript

// Код ниндзя (найди отличие)
let data = 1;
let data1 = 2;
let data2 = 3;
let data3 = 4;

// Или ещё "лучше"
let result;
let resalt;
let rezult;
let res;

// Правильный подход
const userCount = 1;
const productCount = 2;
const orderCount = 3;

2.4 Сезонные имена

javascript

// Код ниндзя (зимой работает, летом - нет)
let summer = 25;
let winter = 100;
let spring = calculate();

// Через месяц никто не помнит, что summer - это температура
const temperature = 25;
const maxLimit = 100;

Часть 3. Магия чисел и строк (или "Почему 42?")

3.1 Магические числа

javascript

// Код ниндзя
if (status === 404) {}
setTimeout(fn, 5000);
if (width > 768) {}

// Никто не знает, что означают эти числа

Расшифровка ниндзя: "Ну, 404 - это же все знают... А 5000 - это пять секунд... 768 - ну, планшеты..."

Правильный код:

javascript

const HTTP_NOT_FOUND = 404;
const REFRESH_INTERVAL_MS = 5000;
const TABLET_BREAKPOINT = 768;

if (status === HTTP_NOT_FOUND) {}
setTimeout(fn, REFRESH_INTERVAL_MS);
if (width > TABLET_BREAKPOINT) {}

3.2 Магические строки

javascript

// Код ниндзя
if (user.role === "admin") {}
if (error.message === "timeout") {}

// А если опечатка? "amin", "timout" - ищи потом

Правильный код:

javascript

const USER_ROLES = {
ADMIN: "admin",
MODERATOR: "moderator",
USER: "user"
};

const ERROR_TYPES = {
TIMEOUT: "timeout",
NETWORK: "network",
VALIDATION: "validation"
};

if (user.role === USER_ROLES.ADMIN) {}
if (error.message === ERROR_TYPES.TIMEOUT) {}

Часть 4. Искусство вложенности (или "Пирамида смерти")

Ниндзя обожает вложенные условия. Чем глубже - тем лучше.

4.1 Пирамида из if

javascript

// Код ниндзя (5 уровней вложенности)
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.hasPermission) {
if (user.age >= 18) {
if (user.verified) {
return "Доступ разрешён";
} else {
return "Требуется верификация";
}
} else {
return "Возраст меньше 18";
}
} else {
return "Нет прав";
}
} else {
return "Аккаунт не активен";
}
} else {
return "Пользователь не найден";
}
}

Правильный код (ранние возвраты):

javascript

function processUser(user) {
if (!user) return "Пользователь не найден";
if (!user.isActive) return "Аккаунт не активен";
if (!user.hasPermission) return "Нет прав";
if (user.age < 18) return "Возраст меньше 18";
if (!user.verified) return "Требуется верификация";

return "Доступ разрешён";
}

4.2 Вложенные тернарники

javascript

// Код ниндзя (угадай, что будет)
const result = a > b ? a > c ? a : c : b > c ? b : c;

// Правильный код
let result;
if (a > b && a > c) result = a;
else if (b > c) result = b;
else result = c;

// Или ещё проще
const result = Math.max(a, b, c);

Часть 5. Побочные эффекты в тени

Ниндзя любит, когда функция делает больше, чем обещает.

5.1 Функция-сюрприз

javascript

// Код ниндзя (меняет глобальную переменную)
let globalCounter = 0;

function process(data) {
globalCounter++;
// Сюрприз!
return data.map(x => x * 2);
}

// Кто бы мог подумать, что process меняет счётчик?

Правильный код (чистая функция):

javascript

function process(data) {
return data.map(x => x * 2);
}

// Счётчик увеличиваем явно
let callCount = 0;
callCount++;
process(data);

5.2 Мутация аргументов

javascript

// Код ниндзя (изменяет исходный массив)
function addItem(items, newItem) {
items.push(newItem);
// Мутирует!
return items;
}

const myList = [1, 2, 3];
const newList = addItem(myList, 4);
console.log(myList);
// [1, 2, 3, 4] (сюрприз!)

Правильный код (копия):

javascript

function addItem(items, newItem) {
return [...items, newItem];
}

const myList = [1, 2, 3];
const newList = addItem(myList, 4);
console.log(myList);
// [1, 2, 3] (не изменился)

Часть 6. Неявные преобразования (игра в угадайку)

Ниндзя обожает == и неявные преобразования. Это создаёт атмосферу тайны.

6.1 Сравнение с подвохом

javascript

// Код ниндзя
if (value == "1") {
// Сработает для 1, "1", true, [1], и т.д.
}

// Что здесь true?
if ([] == false) {}
// true
if (null == undefined) {}
// true
if (" \t\n" == 0) {}
// true

Правильный код (явность):

javascript

if (value === "1") {
// Только для строки "1"
}

// Если нужно несколько вариантов
if (value === "1" || value === 1) {}

6.2 Сложение с разными типами

javascript

// Код ниндзя
let result = "5" + 3 - 2;
// Чему равно? 51? 6? 53-2=51? или 5+3-2=6?

// Правильно: 51
// Сначала "5" + 3 = "53", потом "53" - 2 = 51

Правильный код (явные преобразования):

javascript

const str = "5";
const num = 3;
const result = Number(str) + num - 2;
// 6

Часть 7. Инструментарий ниндзя (опасные техники)

7.1 continue на сложных условиях

javascript

// Код ниндзя (попробуй понять логику)
for (let i = 0; i < 100; i++) {
if (i % 2 === 0) continue;
if (i % 3 === 0) continue;
if (i % 5 === 0) continue;
if (i % 7 === 0) continue;
console.log(i);
}
// Выводит простые числа? Не совсем...

Правильный код:

javascript

const isPrime = (n) => {
for (let i = 2; i < n; i++) {
if (n % i === 0) return false;
}
return n > 1;
};

for (let i = 0; i < 100; i++) {
if (isPrime(i)) {
console.log(i);
}
}

7.2 Деструктуризация с переименованием в зло

javascript

// Код ниндзя (переименовывает всё)
const { a: x, b: y, c: z } = obj;

// Кто помнит, что такое a, b, c?

Правильный код (осмысленные имена):

javascript

const { name: userName, age: userAge, city: userCity } = user;

7.3 Цепочки методов без форматирования

javascript

// Код ниндзя (одна строка)
const result = users.filter(u => u.active).map(u => u.name).sort().join(", ");

// Правильный код (читаемая цепочка)
const result = users
.filter(user => user.active)
.map(user => user.name)
.sort()
.join(", ");

Часть 8. "Умные" трюки, которые бесят коллег

8.1 Побитовые операторы для округления

javascript

// Код ниндзя (экономия 3 символов)
const rounded = ~~12.345;
// 12

// Ниндзя гордится: "Я знаю побитовые операции!"

// Понятный код
const rounded = Math.floor(12.345);

8.2 Использование запятой

javascript

// Код ниндзя (одна строка - много действий)
let a = (1, 2, 3, 4, 5);
// a = 5

// В цикле
for (let i = 0, j = 10; i < j; i++, j--) {}

// Понятно только ниндзя

Правильный код: не используйте оператор запятой там, где можно обойтись без него.

8.3 Инкремент в сложных выражениях

javascript

// Код ниндзя
let i = 0;
let result = i++ + ++i + i--;

// Чему равно? (спойлер: 4)
// Но зачем так писать?

Правильный код: инкремент на отдельной строке.

javascript

let i = 0;
i++;
let result = i + i;
i--;

Часть 9. Код-ниндзя в действии (реальные примеры)

9.1 Функция, которая делает всё

javascript

// Код ниндзя (100 строк, одна функция)
function doEverything(param) {
// валидация
if (!param) return;
// преобразование
let x = param.split(',');
// логика
for (let i = 0; i < x.length; i++) {
// ещё логика
if (x[i] > 10) {
// и ещё
someFunction(x[i]);
}
}
// сохранение
saveToDB(x);
// уведомление
sendEmail(x);
// рендеринг
renderUI(x);
// возврат
return x;
}

Правильный код (разделение ответственности):

javascript

function processUserInput(input) {
const validated = validateInput(input);
if (!validated) return null;

const transformed = transformData(validated);
await saveToDatabase(transformed);
await notifyUser(transformed);
renderUI(transformed);

return transformed;
}

9.2 Класс-монстр

javascript

// Код ниндзя (всё в одном классе)
class SuperClass {
// 50 методов, включая:
validateEmail() {}
saveToDatabase() {}
renderButton() {}
calculateTax() {}
sendSMS() {}
parseCSV() {}
// ...
}

Правильный код (разделение):

javascript

class EmailValidator {}
class DatabaseService {}
class ButtonRenderer {}
class TaxCalculator {}
class NotificationService {}
class CSVParser {}

Часть 10. Как распознать ниндзя в команде

Признаки:

  • Переменные: a, b, c, d, e, f, g...
  • Функции: doSomething(), process(), handle(), run()
  • Комментарии отсутствуют или содержат только // fix this later
  • Функции длиннее 100 строк
  • Вложенность > 3 уровней
  • Использование == вместо ===
  • Магические числа по всему коду

Что делать, если вы ниндзя:

  1. Перестаньте доказывать, что вы умнее всех
  2. Начните писать код для людей
  3. Используйте линтеры и форматтеры
  4. Просите коллег делать code review
  5. Переписывайте старый код, когда его сложно понять

Часть 11. Лечение ниндзя-кода

11.1 Линтеры - ваше спасение

json

{
"rules": {
"no-magic-numbers": "warn",
"id-length": ["error", { "min": 2 }],
"max-depth": ["error", 3],
"max-lines-per-function": ["warn", 30],
"complexity": ["error", 10]
}
}

11.2 Code review

Каждый PR должен проходить ревью. Если reviewer не понял код - ниндзя проиграл.

11.3 Рефакторинг

Постепенно переписывайте сложные участки. Одна маленькая функция в день - и через месяц код станет читаемым.

Итог: Манифест анти-ниндзя

  1. Код читают люди - пишите для человека, который будет поддерживать ваш код через год.
  2. Имена имеют значение - хорошее имя заменяет комментарий.
  3. Простота - высшее мастерство - если код можно написать проще, напишите проще.
  4. Явность лучше неявности - используйте ===, явные преобразования, понятные условия.
  5. Короткие функции - легче тестировать, отлаживать, понимать.
  6. Комментируйте "почему" - что понятно из кода.
  7. Будьте предсказуемы - никаких побочных эффектов, мутации аргументов, сюрпризов.

Финальный тест (выберите правильный вариант):

javascript

// Вариант ниндзя
const x = (a,b) => a+b*2-3/4;

// Вариант джедая
function calculateDiscountedPrice(price, quantity) {
const DISCOUNT = 2;
const TAX_RATE = 0.75;
const subtotal = price * quantity;
const discount = DISCOUNT;
const tax = subtotal * TAX_RATE;
return subtotal - discount + tax;
}

Ответ очевиден.

Помните: ниндзя-код - это не признак мастерства. Это признак неуверенности. Настоящий профессионал пишет код, который может понять любой. Потому что настоящая суперсила - это не умение писать сложный код, а умение делать сложные вещи простыми.

------------------------------------------------------------------------------------

------------------------------------------------------------------------------------

Охотники за багами: Полное руководство по автоматическому тестированию с Mocha (и почему вы больше никогда не напишете console.log для проверки)

Вы написали функцию. Она работает. Вы добавляете новую фичу. Старая ломается. Вы чините старую. Новая отваливается. Знакомо?

Ручное тестирование - это игра в угадайку. Вы никогда не можете быть уверены, что изменения в одном месте не разнесли в щепки три других. Именно поэтому профессионалы пишут тесты.

Добро пожаловать в мир Mocha - фреймворка, который превращает хаос регрессий в стройную систему зеленых галочек.

Часть 1. Что такое Mocha и зачем он нужен?

Mocha - это тестовый фреймворк для JavaScript. Он работает везде: в браузере и в Node.js. Но сам по себе Mocha не умеет проверять результаты. Для этого нужны assertion-библиотеки, и главная звезда в этой вселенной - Chai .

Связка Mocha + Chai - это золотой стандарт тестирования в JavaScript-сообществе. Mocha организует и запускает тесты, а Chai предоставляет красивые и понятные проверки .

javascript

// Mocha говорит: "Опиши, что тестируем"
describe("Калькулятор", function() {

// Mocha говорит: "Вот конкретный тест"
it("должен правильно складывать числа", function() {

// Chai проверяет: "Результат должен быть 5"
expect(2 + 3).to.equal(5);
});
});

Почему BDD?

Mocha из коробки поддерживает подход Behavior Driven Development (разработка через поведение). BDD - это три в одном: тесты, документация и примеры использования .

Когда вы пишете:

javascript

describe("Возведение в степень", function() {
it("2 в степени 3 должно равняться 8", function() {
expect(pow(2, 3)).to.equal(8);
});
});

Вы не просто проверяете код. Вы создаете живую документацию, которую любой разработчик может прочитать и понять, что должна делать функция .

Часть 2. Первые шаги: Настройка Mocha

Установка

Самый простой способ - установить Mocha локально в проект :

bash

npm install --save-dev mocha chai

Простая HTML-страница для тестов (браузер)

Если вы тестируете фронтенд-код, вам понадобится HTML-страница :

html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Стили для красивого отображения результатов -->
<link rel="stylesheet" href="node_modules/mocha/mocha.css">
<!-- Подключаем Mocha -->
<script src="node_modules/mocha/mocha.js"></script>
<!-- Подключаем Chai -->
<script src="node_modules/chai/chai.js"></script>
<script>
// Настраиваем Mocha на BDD-стиль
mocha.setup('bdd');
// Объявляем expect глобально для удобства
const expect = chai.expect;
</script>
</head>
<body>
<!-- Сюда Mocha выведет результаты -->
<div id="mocha"></div>

<!-- Ваш код для тестирования -->
<script src="calculator.js"></script>

<!-- Файл с тестами -->
<script src="calculator.test.js"></script>

<!-- Запускаем тесты! -->
<script>
mocha.run();
</script>
</body>
</html>

Запуск в Node.js

Для бэкенда достаточно выполнить в терминале :

bash

npx mocha

Mocha автоматически найдет файлы в папке test с именами, оканчивающимися на .test.js или .spec.js.

Часть 3. Анатомия теста: describe, it и assert

describe - Группировка тестов

describe создает "корзину" для связанных тестов. Их можно вкладывать друг в друга :

javascript

describe("Математические операции", function() {

describe("Сложение", function() {
// тесты для сложения
});

describe("Вычитание", function() {
// тесты для вычитания
});
});

it - Отдельный тест

it - это конкретная проверка. Первый аргумент - человекочитаемое описание, второй - функция с тестом :

javascript

it("должен возвращать 4 при сложении 2 и 2", function() {
expect(add(2, 2)).to.equal(4);
});

expect, assert, should - Три лица Chai

Chai предлагает три стиля assertions :

1. expect (BDD-стиль) - самый читаемый:

javascript

expect(result).to.equal(5);
expect(user).to.be.an('object').that.has.property('name');

2. assert (классический стиль):

javascript

assert.equal(result, 5);
assert.typeOf(user, 'object');

3. should (стиль, расширяющий прототипы):

javascript

result.should.equal(5);
user.should.be.an('object');

Часть 4. Тестируем асинхронный код

Асинхронность - главная боль в тестировании. Mocha предлагает три способа справиться с ней .

Способ 1: Callback done

Добавьте параметр done в функцию теста. Mocha будет ждать, пока вы его вызовете :

javascript

describe("Асинхронная операция", function() {
it("должна выполниться за 1 секунду", function(done) {
setTimeout(function() {
expect(true).to.be.true;
done();
// Говорим Mocha: "Всё, я закончил"
}, 1000);
});
});

⚠️ Важно: Если вы забудете вызвать done, тест упадет по таймауту .

Способ 2: Возвращаем Promise

Если ваша функция возвращает Promise, просто верните его из теста :

javascript

it("должен загрузить пользователя", function() {
return fetchUser(1).then(user => {
expect(user.name).to.equal("Анна");
});
});

Способ 3: Async/Await (современный и красивый)

Самый читаемый способ - использовать async/await :

javascript

it("должен загрузить пользователя", async function() {
const user = await fetchUser(1);
expect(user.name).to.equal("Анна");
});

Часть 5. Хуки: Подготовка и очистка

Часто перед каждым тестом нужно что-то настроить (создать объект, подключиться к БД), а после - почистить. Для этого есть хуки :

javascript

describe("База данных пользователей", function() {

// Выполняется один раз ПЕРЕД всеми тестами в describe
before(function() {
console.log("Подключаемся к БД...");
});

// Выполняется один раз ПОСЛЕ всех тестов
after(function() {
console.log("Отключаемся от БД...");
});

// Выполняется ПЕРЕД КАЖДЫМ тестом
beforeEach(function() {
console.log("Создаём свежего пользователя");
this.user = new User("Анна");
});

// Выполняется ПОСЛЕ КАЖДОГО теста
afterEach(function() {
console.log("Удаляем пользователя");
delete this.user;
});

it("пользователь имеет имя", function() {
expect(this.user.name).to.equal("Анна");
});
});

Часть 6. Реальный пример: Разработка через тесты (TDD)

Давайте разберем классический сценарий разработки функции возведения в степень pow(x, n) .

Шаг 1: Пишем спецификацию (тесты)

javascript

describe("pow", function() {

it("2 в степени 3 = 8", function() {
expect(pow(2, 3)).to.equal(8);
});

it("3 в степени 4 = 81", function() {
expect(pow(3, 4)).to.equal(81);
});

it("Любое число в степени 0 = 1", function() {
expect(pow(5, 0)).to.equal(1);
});
});

Шаг 2: Запускаем тесты (они красные)

Пока функции pow нет - тесты падают. Это нормально.

Шаг 3: Пишем минимальную реализацию

javascript

function pow(x, n) {
if (n === 0) return 1;
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}

Шаг 4: Запускаем снова (зеленые тесты)

Ура! Все тесты проходят.

Шаг 5: Добавляем больше тестов

javascript

it("для отрицательных n возвращает NaN", function() {
expect(pow(2, -1)).to.be.NaN;
});

Новый тест падает. Возвращаемся к реализации и добавляем обработку .

Часть 7. Продвинутые техники

Тестирование ошибок

javascript

it("должен бросить ошибку при пустом имени", function() {
expect(() => {
new User("");
}).to.throw(Error, "Имя не может быть пустым");
});

Исключение тестов (skip) и запуск одного (only)

javascript

// Пропустить этот тест
it.skip("этот тест временно не нужен", function() {
// ...
});

// Запустить только этот тест (игнорировать остальные)
it.only("я хочу проверить только это место", function() {
// ...
});

Работа с таймаутами

Для медленных операций можно увеличить таймаут :

javascript

it("должен загрузить большой файл", function() {
this.timeout(10000);
// 10 секунд
return loadLargeFile();
});

Часть 8. Mocha и TypeScript

Mocha отлично работает с TypeScript. Установите ts-node и настройте запуск:

bash

npm install --save-dev typescript ts-node @types/mocha @types/chai

Запуск:

bash

npx mocha --require ts-node/register tests/**/*.spec.ts

Часть 9. Интеграция с IDE

Современные IDE (WebStorm, VS Code) отлично интегрируются с Mocha :

  • Запуск теста - зеленая стрелка рядом с describe или it
  • Отладка - можно поставить брейкпоинт прямо в тесте и запустить его в режиме дебага
  • Покрытие кода - специальные плагины показывают, какие строки не покрыты тестами

Часть 10. Жизненный цикл выполнения тестов

Понимание того, как Mocha выполняет тесты, помогает избежать странных багов :

  1. Mocha загружает все файлы с тестами
  2. Выполняются before-хуки
  3. Для каждого теста:
    Выполняется beforeEach
    Запускается сам тест (it)
    Выполняется afterEach
  4. В конце - after-хуки

Этот порядок гарантирует, что каждый тест начинается с чистого состояния.

Итог: Манифест тестировщика

  1. Тесты - это документация - они рассказывают историю вашего кода.
  2. Красный → Зеленый → Рефакторинг - золотое правило TDD.
  3. Один тест - одна проверка - так проще понять, что именно сломалось .
  4. Не забывайте про асинхронность - используйте async/await или возвращайте Promise.
  5. Хуки для чистоты - beforeEach и afterEach сохраняют тесты независимыми.
  6. Зеленый - не значит "всё работает" - это значит "всё, что мы проверили, работает".

Финальный тест: что выведет этот код?

javascript

describe("Счётчик", function() {
let counter;

beforeEach(function() {
counter = 0;
});

it("увеличивается на 1", function() {
counter++;
expect(counter).to.equal(1);
});

it("начинается с 0 в каждом тесте", function() {
expect(counter).to.equal(0);
});
});

Ответ: Оба теста пройдут. beforeEach создает новый counter = 0 перед каждым it. Тесты не влияют друг на друга.

Mocha - это не просто инструмент. Это дисциплина, которая делает ваш код надежнее, а вас - увереннее. Пишите тесты, запускайте их, и пусть все ваши сборки будут зелёными

---------------------------------------------------------------------------------

---------------------------------------------------------------------------------

Мосты между эпохами: Искусство полифилов (или как запустить ES2024 в Internet Explorer)

Представьте: вы написали красивый современный код с optional chaining, nullish coalescing и методами массивов, о которых в 2015 году только мечтали. Вы горды собой. Вы открываете проект в старом браузере... и всё падает. Чёрный экран. Ошибки в консоли.

Знакомо?

Полифилы - это спасательный круг. Это кусочки кода, которые добавляют современные возможности в старые браузеры. Они - причина, почему мы можем использовать Array.prototype.includes и не проверять, поддерживает ли его браузер.

Добро пожаложаловать в мир, где прошлое и будущее уживаются в одном проекте.

Часть 1. Что такое полифил и почему он так называется?

Полифил (polyfill) - это код, который "заполняет" (fill) пробелы (poly) в старых браузерах, добавляя отсутствующие функции.

Термин придумал Реми Шарп, вдохновившись шпаклёвкой для стен (Polyfilla - бренд шпаклёвки). Как шпаклёвка скрывает трещины в стене, так полифил скрывает недостатки браузера.

javascript

// Если браузер не понимает метод .includes
if (!Array.prototype.includes) {
// Добавляем его сами
Array.prototype.includes = function(searchElement) {
return this.indexOf(searchElement) !== -1;
};
}

После этого кода даже Internet Explorer 11 поймёт, что такое [1, 2, 3].includes(2).

Часть 2. Полифил vs Транспилятор: В чём разница?

Эти два понятия часто путают, но они решают разные задачи:

-4

Аналогия: Транспилятор - переводчик с французского на русский. Полифил - это когда у собеседника нет слова "компьютер", и вы объясняете пальцами.

В реальной жизни Babel и полифилы работают в паре:

  1. Babel переписывает современный синтаксис
  2. Полифилы добавляют отсутствующие API

javascript

// Исходный код
const hasItem = [1, 2, 3].includes(2);

// После Babel (синтаксис не изменился, includes остался)
const hasItem = [1, 2, 3].includes(2);

// Без полифила в IE11 → ошибка
// С полифилом → работает

Часть 3. Самые популярные полифилы (и зачем они нужны)

3.1 Promise - король асинхронности

Старые браузеры не знают, что такое Promise:

javascript

if (!window.Promise) {
// Нужен полифил (например, от Google)
// Обычно используют библиотеку 'es6-promise'
}

// Использование
fetch('/api/data')
.then(response => response.json())
.catch(error => console.error(error));

3.2 Object.assign - копирование объектов

javascript

if (!Object.assign) {
Object.assign = function(target, ...sources) {
for (let source of sources) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
return target;
};
}

3.3 Array.prototype.includes - проверка наличия

javascript

if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
if (this == null) throw new TypeError('"this" is null or not defined');
const o = Object(this);
const len = o.length >>> 0;
if (len === 0) return false;
let n = fromIndex | 0;
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (o[k] === searchElement) return true;
k++;
}
return false;
};
}

3.4 String.prototype.startsWith / endsWith

javascript

if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.substring(position, position + searchString.length) === searchString;
};
}

3.5 Array.from - создание массива из итерируемых объектов

javascript

if (!Array.from) {
Array.from = function(arrayLike, mapFn, thisArg) {
const items = [];
for (let i = 0; i < arrayLike.length; i++) {
items.push(arrayLike[i]);
}
if (mapFn) {
return items.map(mapFn, thisArg);
}
return items;
};
}

Часть 4. Как НЕ надо писать полифилы (источник багов)

4.1 Не проверяйте существование неправильно

javascript

// Плохо (использует неправильное условие)
if (!Array.includes) {
Array.prototype.includes = function() { ... }
}

// Хорошо
if (!Array.prototype.includes) {
Array.prototype.includes = function() { ... }
}

4.2 Не добавляйте полифилы, которые уже есть

javascript

// Плохо (затирает родную, возможно, более быструю реализацию)
Array.prototype.map = function(callback) {
// своя медленная реализация
};

// Хорошо
if (!Array.prototype.map) {
Array.prototype.map = function(callback) {
// своя реализация только если нет родной
};
}

4.3 Не ломайте спецификацию

javascript

// Плохо (не соответствует спецификации)
if (!Array.prototype.includes) {
Array.prototype.includes = function(item) {
return this.indexOf(item) > -1;
// Не обрабатывает fromIndex
};
}

// Хорошо (полная реализация)
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
// полная реализация с учётом fromIndex
};
}

Часть 5. Стратегии подключения полифилов

5.1 Всё и сразу (просто, но много кода)

html

<!-- Подключаем полифилы для всего -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>

5.2 Условная загрузка (только для старых браузеров)

html

<!-- Только для Internet Explorer -->
<!--[if IE]>
<script src="polyfills/ie11.js"></script>
<![endif]-->

5.3 Современный подход: feature detection

Проверяем поддержку и загружаем только то, что нужно:

javascript

async function loadPolyfills() {
const polyfills = [];

if (!window.Promise) polyfills.push('promise');
if (!Object.assign) polyfills.push('object-assign');
if (!Array.prototype.includes) polyfills.push('array-includes');

if (polyfills.length) {
await import(`https://polyfill.io/v3/polyfill.min.js?features=${polyfills.join(',')}`);
}
}

loadPolyfills();

Часть 6. Популярные библиотеки полифилов

6.1 core-js (самая популярная)

Это стандарт де-факто. Используется в Babel, Webpack и многих других инструментах.

bash

npm install core-js

javascript

// Подключение конкретных полифилов
import 'core-js/actual/array/includes';
import 'core-js/actual/promise';

// Или всех сразу (но это много)
import 'core-js/actual';

6.2 Polyfill.io (сервис)

Умный сервис, который отдаёт только нужные полифилы под конкретный браузер.

html

<!-- Просто подключите этот скрипт -->
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

<!-- Он сам определит браузер и отдаст нужные полифилы -->

6.3 Babel Polyfill (устаревший)

Раньше использовали @babel/polyfill, но теперь он объединился с core-js.

bash

npm install @babel/polyfill

Часть 7. Пишем свой полифил: пошаговое руководство

Допустим, мы хотим добавить метод Array.prototype.last() (возвращает последний элемент массива).

Шаг 1: Проверяем, нужен ли полифил

javascript

if (!Array.prototype.last) {
// Добавляем
}

Шаг 2: Пишем реализацию

javascript

if (!Array.prototype.last) {
Array.prototype.last = function() {
if (this.length === 0) {
return undefined;
}
return this[this.length - 1];
};
}

Шаг 3: Проверяем корректность

javascript

// Тест
const arr = [1, 2, 3];
console.log(arr.last());
// 3
console.log([].last());
// undefined

Шаг 4: Убеждаемся, что не ломаем существующий код

javascript

// Не изменяем поведение, если метод уже есть
if (Array.prototype.last) {
console.warn('Array.prototype.last уже существует');
}

Часть 8. Реальный кейс: Поддержка IE11 в 2024 году

Да, IE11 всё ещё существует. И вот минимальный набор полифилов для него:

javascript

// Самый необходимый минимум
const ie11Polyfills = [
'Promise',
'Object.assign',
'Array.from',
'Array.prototype.includes',
'Array.prototype.find',
'String.prototype.startsWith',
'String.prototype.includes',
'fetch',
// отдельный полифил
'Symbol'
];

// Подключение через polyfill.io
const script = document.createElement('script');
script.src = `https://polyfill.io/v3/polyfill.min.js?features=${ie11Polyfills.join(',')}`;
document.head.appendChild(script);

Часть 9. Производительность и полифилы

9.1 Нативные методы быстрее

javascript

// Родная реализация (быстрая)
if (Array.prototype.includes) {
// Используем родной метод
}

// Полифил (медленнее)
if (!Array.prototype.includes) {
// Используем свою реализацию
}

9.2 Не полифильте то, что не нужно

javascript

// Плохо (тяжёлый полифил для всех)
import 'core-js/actual';

// Хорошо (только нужное)
import 'core-js/actual/promise';
import 'core-js/actual/array/includes';

9.3 Используйте CDN с умом

html

<!-- Polyfill.io кэширует и сжимает -->
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

Часть 10. Полифилы в современных инструментах

10.1 Babel с preset-env

json

{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3,
"targets": "> 0.25%, not dead"
}]
]
}

Опция "useBuiltIns": "usage" автоматически добавляет только нужные полифилы.

10.2 Webpack и core-js

javascript

// webpack.config.js
module.exports = {
entry: ['core-js/stable', './src/index.js']
};

10.3 Vite и современные браузеры

Vite по умолчанию нацелен на современные браузеры, но можно настроить:

javascript

// vite.config.js
export default {
build: {
target: 'es2015',
polyfill: true
}
};

Часть 11. Ошибки при работе с полифилами

11.1 Полифилы для глобальных объектов

javascript

// Плохо (ломает глобальный объект)
window.Promise = function() {};

// Хорошо (проверка и осторожность)
if (!window.Promise) {
window.Promise = MyPromiseImplementation;
}

11.2 Полифилы, зависящие от других полифилов

javascript

// Плохо (Promise может не существовать)
if (!Array.prototype.includesAsync) {
Array.prototype.includesAsync = async function(item) {
await wait(100);
// Promise нужен!
return this.includes(item);
};
}

// Хорошо (проверяем зависимости)
if (window.Promise && !Array.prototype.includesAsync) {
Array.prototype.includesAsync = async function(item) {
await wait(100);
return this.includes(item);
};
}

11.3 Полифилы, изменяющие прототипы встроенных объектов

javascript

// Спорно (могут быть конфликты)
if (!Array.prototype.first) {
Array.prototype.first = function() {
return this[0];
};
}

// Безопаснее (отдельная функция)
function first(arr) {
return arr[0];
}

Часть 12. Будущее полифилов

С выходом новых версий ECMAScript необходимость в полифилах уменьшается:

  • Браузеры автоматически обновляются - Chrome, Firefox, Edge обновляются каждые 4-6 недель
  • Поддержка старых браузеров падает - многие компании отказываются от IE11
  • Современные инструменты - Vite, ESBuild работают с современным кодом

Но полифилы не исчезнут полностью:

  • Новые API (например, URLPattern, CompressionStream)
  • Нишевые браузеры (умные телевизоры, киоски, банкоматы)
  • Корпоративные среды с замороженными версиями браузеров

Итог: Манифест полифилов

  1. Проверяйте перед добавлением - if (!Object.assign) перед реализацией.
  2. Не ломайте спецификацию - полифил должен вести себя как родной метод.
  3. Загружайте только нужное - не тащите всё core-js ради одного метода.
  4. Используйте feature detection - проверяйте поддержку, а не версию браузера.
  5. Доверяйте профессионалам - core-js и polyfill.io делают работу лучше.
  6. Тестируйте полифилы - они тоже могут содержать баги.
  7. Помните о производительности - полифилы медленнее нативных реализаций.

Финальный тест (проверьте себя):

javascript

// Что произойдёт в старом браузере без полифила?
const hasTwo = [1, 2, 3].includes(2);

// Что произойдёт в старом браузере с полифилом?
if (!Array.prototype.includes) {
Array.prototype.includes = function(item) {
return this.indexOf(item) !== -1;
};
}
const hasTwo = [1, 2, 3].includes(2);

Ответ: Без полифила - ошибка TypeError: [1,2,3].includes is not a function. С полифилом - true.

Полифилы - это мост между прошлым и будущим JavaScript. Они позволяют писать современный код сегодня, не отказываясь от пользователей со старыми браузерами. Используйте их с умом, и ваш код будет работать везде. А когда старые браузеры уйдут в историю, полифилы можно будет просто удалить. Но до тех пор они остаются незаменимым инструментом в арсенале веб-разработчика.