Охотники за багами: Искусство отладки 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. Пошаговая отладка - Контроль времени
Когда код на паузе, вы управляете временем:
Практический пример:
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 панель покажет, где именно.
- Нажмите ● (Record)
- Выполните действие, которое тормозит
- Остановите запись
Что смотреть:
- Красные полоски - долгие задачи (Long Tasks), блокирующие UI
- Стрелки вверх-вниз - принудительный reflow/repaint
- Цветные блоки - время на JS (жёлтый), рендеринг (фиолетовый), покраска (зелёный)
Memory Tab
Утечка памяти? Memory панель поможет:
- Heap snapshot - снимок памяти в моменте
- Allocation instrumentation - запись выделений памяти в реальном времени
- 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: "Ничего не происходит при клике"
План:
- Открыть Sources → Event Listener Breakpoints → Mouse → click
- Нажать на элемент
- Код остановится на первом обработчике
- Пройти Step over/into, пока не найдёте свою функцию
- Проверить, что условие срабатывает (if (isActive))
Сценарий #2: "Значение меняется непонятно кем"
План:
- Правый клик на строке с переменной → "Break on value change" (не везде)
- Или найти все места изменения:javascript// Добавьте временно в код
Object.defineProperty(window, 'problemVar', {
set(value) {
debugger;
this._value = value;
},
get() { return this._value; }
});
Сценарий #3: "Бесконечный цикл"
План:
- Включить Pause on exceptions
- Нажать F8 (Resume)
- Браузер покажет, где зациклилось (обычно через 5-10 секунд)
Сценарий #4: "Не загружается скрипт"
План:
- Network Tab → посмотреть статус (404? 500?)
- Sources → нажать Ctrl+P → искать файл
- Если не находится → скрипт не загрузился
Часть 12. Горячие клавиши (шпаргалка)
Часть 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 =.
Итог: Манифест отладчика
- Перестаньте гадать - используйте брейкпоинты.
- Перестаньте засорять код - используйте logpoints.
- Перестаньте жать "Step into" 100 раз - используйте conditional breakpoints.
- Перестаньте теряться в асинхронности - используйте async stack traces.
- Перестаньте гадать, где память - используйте 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 ошибок
Итог: Манифест стиля
- Код читают люди - пишите для человека, который будет поддерживать ваш код (это может быть вы через полгода).
- Будьте последовательны - любой стиль лучше, чем смесь стилей.
- Автоматизируйте - Prettier + ESLint решают 90% проблем.
- Имена имеют значение - хорошее имя заменяет комментарий.
- Короткие функции - легче тестировать, отлаживать, понимать.
- Ранние возвраты - убивают вложенность.
- Не будьте умным - понятный код лучше "гениального".
И последнее: самый лучший стиль - это тот, которого придерживается вся команда. Договоритесь, запишите в 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 Закон "Умирающих комментариев"
Комментарии устаревают быстрее кода. Люди забывают их обновлять. Хороший комментарий - тот, который напоминает о себе (например, ссылкой на баг-трекер, который закроют при исправлении).
Итог: Манифест комментатора
- Пишите код, который не нуждается в комментариях - хорошие имена, короткие функции, понятная логика.
- Комментируйте "почему", а не "что" - что видно из кода, почему - нет.
- Не храните историю в комментариях - для этого есть Git.
- Используйте JSDoc - это стандарт, его понимают IDE и генераторы документации.
- TODO, FIXME, HACK - ваши друзья - но возвращайтесь к ним.
- Удаляйте закомментированный код - он создаёт ложное чувство безопасности.
- Будьте вежливы - комментарии читают люди.
Финальный тест: какой комментарий лучше?
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 уровней
- Использование == вместо ===
- Магические числа по всему коду
Что делать, если вы ниндзя:
- Перестаньте доказывать, что вы умнее всех
- Начните писать код для людей
- Используйте линтеры и форматтеры
- Просите коллег делать code review
- Переписывайте старый код, когда его сложно понять
Часть 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 Рефакторинг
Постепенно переписывайте сложные участки. Одна маленькая функция в день - и через месяц код станет читаемым.
Итог: Манифест анти-ниндзя
- Код читают люди - пишите для человека, который будет поддерживать ваш код через год.
- Имена имеют значение - хорошее имя заменяет комментарий.
- Простота - высшее мастерство - если код можно написать проще, напишите проще.
- Явность лучше неявности - используйте ===, явные преобразования, понятные условия.
- Короткие функции - легче тестировать, отлаживать, понимать.
- Комментируйте "почему" - что понятно из кода.
- Будьте предсказуемы - никаких побочных эффектов, мутации аргументов, сюрпризов.
Финальный тест (выберите правильный вариант):
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 выполняет тесты, помогает избежать странных багов :
- Mocha загружает все файлы с тестами
- Выполняются before-хуки
- Для каждого теста:
Выполняется beforeEach
Запускается сам тест (it)
Выполняется afterEach - В конце - after-хуки
Этот порядок гарантирует, что каждый тест начинается с чистого состояния.
Итог: Манифест тестировщика
- Тесты - это документация - они рассказывают историю вашего кода.
- Красный → Зеленый → Рефакторинг - золотое правило TDD.
- Один тест - одна проверка - так проще понять, что именно сломалось .
- Не забывайте про асинхронность - используйте async/await или возвращайте Promise.
- Хуки для чистоты - beforeEach и afterEach сохраняют тесты независимыми.
- Зеленый - не значит "всё работает" - это значит "всё, что мы проверили, работает".
Финальный тест: что выведет этот код?
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 Транспилятор: В чём разница?
Эти два понятия часто путают, но они решают разные задачи:
Аналогия: Транспилятор - переводчик с французского на русский. Полифил - это когда у собеседника нет слова "компьютер", и вы объясняете пальцами.
В реальной жизни Babel и полифилы работают в паре:
- Babel переписывает современный синтаксис
- Полифилы добавляют отсутствующие 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)
- Нишевые браузеры (умные телевизоры, киоски, банкоматы)
- Корпоративные среды с замороженными версиями браузеров
Итог: Манифест полифилов
- Проверяйте перед добавлением - if (!Object.assign) перед реализацией.
- Не ломайте спецификацию - полифил должен вести себя как родной метод.
- Загружайте только нужное - не тащите всё core-js ради одного метода.
- Используйте feature detection - проверяйте поддержку, а не версию браузера.
- Тестируйте полифилы - они тоже могут содержать баги.
- Помните о производительности - полифилы медленнее нативных реализаций.
Финальный тест (проверьте себя):
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. Они позволяют писать современный код сегодня, не отказываясь от пользователей со старыми браузерами. Используйте их с умом, и ваш код будет работать везде. А когда старые браузеры уйдут в историю, полифилы можно будет просто удалить. Но до тех пор они остаются незаменимым инструментом в арсенале веб-разработчика.