Бесконечность не предел: Полное руководство по циклам while и for (и как не положить браузер)
Циклы - это сердце любого алгоритма. Они делают скучную работу за нас: перебирают тысячи элементов, ждут нужного условия, строят сложные структуры. Но циклы - это еще и самый быстрый способ заморозить вкладку, если вы не знаете, что делаете.
В JavaScript есть целое семейство циклов, но сегодня мы разберем двух китов: while и for. Они разные по характеру, но одинаково мощные.
Часть 1. while - Цикл-оптимист
while говорит: "Пока условие истинно - делай. Когда станет ложным - остановись".
javascript
while (condition) {
// тело цикла
}
Важнейший момент: условие проверяется перед каждой итерацией. Если изначально condition === false, тело не выполнится ни разу.
Простой пример:
javascript
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
// Выведет: 0, 1, 2, 3, 4
Когда использовать while?
✅ Когда количество итераций неизвестно заранее
javascript
// Ждем, пока пользователь введет корректное значение
let userInput = null;
while (userInput === null || userInput.trim() === "") {
userInput = prompt("Введите ваше имя:");
}
✅ Когда условие зависит от внешних факторов
javascript
// Игра: бросаем кубик, пока не выпадет 6
let roll = 0;
while (roll !== 6) {
roll = Math.floor(Math.random() * 6) + 1;
console.log(`Выпало: ${roll}`);
}
console.log("Победа!");
✅ Для рекурсивных обходов (например, DOM)
javascript
// Найти все элементы с классом "hidden" и показать их
let hiddenElements = document.querySelectorAll(".hidden");
while (hiddenElements.length > 0) {
hiddenElements[0].classList.remove("hidden");
// Обновляем коллекцию (живая коллекция!)
hiddenElements = document.querySelectorAll(".hidden");
}
Часть 2. do...while - Цикл-реалист
В отличие от while, этот цикл сначала делает, а потом проверяет. Тело выполнится хотя бы один раз.
javascript
do {
// тело цикла (выполнится минимум 1 раз)
} while (condition);
Пример:
javascript
let number;
do {
number = prompt("Введите число больше 10:");
} while (number <= 10);
console.log(`Спасибо, вы ввели ${number}`);
Классический кейс: меню в консольных приложениях.
javascript
let choice;
do {
console.log("1. Начать игру");
console.log("2. Загрузить игру");
console.log("3. Выйти");
choice = prompt("Ваш выбор:");
if (choice === "1") startGame();
if (choice === "2") loadGame();
} while (choice !== "3");
while vs do...while - что выбрать?
Часть 3. for - Цикл-контрол-фрик
for - самый популярный цикл в JS. Он собирает всё в одной строке: инициализацию, условие, пост-действие.
javascript
for (инициализация; условие; пост-действие) {
// тело цикла
}
Классический пример:
javascript
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 0, 1, 2, 3, 4
Как это работает (пошагово):
- let i = 0 - выполняется один раз при входе
- i < 5 - проверка условия (если false - выход)
- Тело цикла console.log(i)
- i++ - пост-действие
- Переход к шагу 2
Все части for опциональны!
javascript
// Бесконечный цикл (все части пустые)
for (;;) {
console.log("Вечность...");
break; // без break не остановить
}
// Без инициализации (переменная уже объявлена)
let i = 0;
for (; i < 5; i++) { ... }
// Без пост-действия (управление внутри)
for (let i = 0; i < 5; ) {
console.log(i);
i++; // инкремент здесь
}
// Без условия (бесконечный)
for (let i = 0; ; i++) {
if (i > 100) break; // условие выхода внутри
}
Часть 4. Разбор культовых примеров (или "Как шокировать коллег")
Пример #1: for с запятой
javascript
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j);
}
// 0 10
// 1 9
// 2 8
// 3 7
// 4 6
Пример #2: Цикл без тела
javascript
let sum = 0;
for (let i = 1; i <= 100; sum += i, i++);
console.log(sum); // 5050 (всё посчиталось, тело пустое!)
Пример #3: Обратный проход по массиву
javascript
let arr = [1, 2, 3, 4, 5];
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]); // 5, 4, 3, 2, 1
}
Часть 5. Бесконечные циклы (и как их не создать случайно)
Бесконечный цикл - это когда условие никогда не становится false. Результат - зависший браузер и 100% CPU.
Как создать бесконечный цикл (и как этого избежать):
javascript
// ОШИБКА #1: Забыли инкремент
for (let i = 0; i < 10; ) {
console.log(i); // i всегда 0 → бесконечность
}
// ОШИБКА #2: Условие всегда true
let i = 0;
while (i < 10) {
console.log(i);
i--; // уменьшаем вместо увеличения → i уходит в минус
}
// ОШИБКА #3: Сравнение с плавающей точкой
for (let i = 0; i !== 1; i += 0.1) {
console.log(i); // никогда не станет точно 1
}
// Правильно: i < 1
Как прервать бесконечный цикл:
- Ctrl + C (в Node.js)
- Закрыть вкладку (в браузере)
- Использовать debugger и понять, что пошло не так
Часть 6. Управление циклом: break и continue
break - экстренный выход
javascript
// Найти первое число, делящееся на 7
for (let i = 1; i <= 100; i++) {
if (i % 7 === 0) {
console.log(`Первое число: ${i}`);
break; // выход из цикла
}
}
continue - пропуск итерации
javascript
// Вывести только нечетные числа
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue; // четные пропускаем
console.log(i); // 1, 3, 5, 7, 9
}
Метки (labels) - выход из вложенных циклов
javascript
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
console.log("Выходим из обоих циклов");
break outer; // выход из внешнего цикла
}
console.log(i, j);
}
}
// Вывод: 0,0 0,1 0,2 1,0 "Выходим..."
Но предупреждение: метки - это мощно, но они ухудшают читаемость. Используйте с осторожностью.
Часть 7. Производительность (или "Какой цикл быстрее")
Мифов о производительности циклов много. Давайте к фактам:
javascript
let arr = new Array(1000000).fill(1);
// Классический for (самый быстрый)
console.time("for");
for (let i = 0; i < arr.length; i++) {
arr[i] * 2;
}
console.timeEnd("for");
// for...of (медленнее, но читаемее)
console.time("for...of");
for (let value of arr) {
value * 2;
}
console.timeEnd("for...of");
// forEach (средний)
console.time("forEach");
arr.forEach(value => value * 2);
console.timeEnd("forEach");
// while (примерно как for)
console.time("while");
let i = 0;
while (i < arr.length) {
arr[i] * 2;
i++;
}
console.timeEnd("while");
Реальность: Разница заметна только на миллионах операций. Для 99% кода выбирайте читаемость, а не скорость.
Совет по оптимизации: Кешируйте длину массива!
javascript
// Плохо (на каждой итерации вычисляем arr.length)
for (let i = 0; i < arr.length; i++) { }
// Хорошо (вычисляем один раз)
for (let i = 0, len = arr.length; i < len; i++) { }
// Современный вариант (движки оптимизируют, но привычка полезна)
Часть 8. Подводные камни (обязательно к прочтению!)
Камень #1: var в цикле (классическая ловушка)
javascript
// Плохо (старый JS)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Выведет: 3, 3, 3 (все три раза 3!)
// Почему? var имеет функциональную область видимости
// Все три колбэка ссылаются на одну переменную i
// Хорошо (используем let)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Выведет: 0, 1, 2
Камень #2: Изменение коллекции во время итерации
javascript
let arr = [1, 2, 3, 4, 5];
// Удаляем элементы во время прохода
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
arr.splice(i, 1); // удаляем четный элемент
i--; // компенсируем сдвиг индекса!
}
}
console.log(arr); // [1, 3, 5]
// Без i-- пропустили бы элементы!
Камень #3: Асинхронный код в цикле
javascript
// Что выведет?
for (var i = 0; i < 3; i++) {
fetch(`/api/${i}`).then(() => console.log(i));
}
// 3, 3, 3 (если var)
// 3, 3, 3 (если let? да, все равно 3!)
// Потому что fetch асинхронный, цикл закончится раньше
// Решение: IIFE или for...of с async/await
async function processItems() {
for (let i = 0; i < 3; i++) {
await fetch(`/api/${i}`); // ждем каждый запрос
console.log(i); // 0, 1, 2 (по порядку)
}
}
Часть 9. Альтернативы (когда циклы не нужны)
for...of - для перебора итерируемых объектов
javascript
let arr = [10, 20, 30];
for (let value of arr) {
console.log(value); // 10, 20, 30
}
// Работает со строками, Map, Set, NodeList
let str = "hello";
for (let char of str) {
console.log(char); // h, e, l, l, o
}
for...in - для перебора свойств объектов (НО осторожно!)
javascript
let obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]); // a 1, b 2, c 3
}
// Внимание: for...in перебирает и унаследованные свойства!
// Всегда проверяйте hasOwnProperty
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key);
}
}
Array.prototype.forEach - декларативный подход
javascript
let arr = [1, 2, 3];
arr.forEach((item, index) => {
console.log(index, item);
});
// Нельзя прервать (break не работает)
// Нельзя вернуть значение (в отличие от map/filter/reduce)
Часть 10. Реальные паттерны из продакшена
Паттерн #1: Пагинация (загрузка всех страниц)
javascript
async function fetchAllUsers() {
let page = 1;
let allUsers = [];
let hasMore = true;
while (hasMore) {
const response = await fetch(`/api/users?page=${page}`);
const data = await response.json();
allUsers.push(...data.users);
hasMore = data.hasMore;
page++;
}
return allUsers;
}
Паттерн #2: Ретри (повторные попытки)
javascript
async function fetchWithRetry(url, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
attempt++;
if (attempt === maxRetries) throw error;
console.log(`Попытка ${attempt} не удалась. Повтор через ${attempt}s`);
await new Promise(resolve => setTimeout(resolve, attempt * 1000));
}
}
}
Паттерн #3: Обработка DOM-элементов (рекурсивный обход)
javascript
function getAllTextNodes(element) {
let textNodes = [];
let walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);
while (walker.nextNode()) {
let node = walker.currentNode;
if (node.textContent.trim()) {
textNodes.push(node);
}
}
return textNodes;
}
Паттерн #4: Игра (игровой цикл)
javascript
class GameLoop {
constructor() {
this.running = true;
this.lastTime = 0;
}
start() {
this.running = true;
this.loop();
}
loop() {
if (!this.running) return;
const now = performance.now();
const delta = now - this.lastTime;
this.lastTime = now;
this.update(delta);
this.render();
requestAnimationFrame(() => this.loop());
}
stop() {
this.running = false;
}
}
Итог: Манифест циклов
- for - когда знаете количество итераций или перебираете массив.
- while - когда не знаете, сколько итераций, и условие может быть false с самого начала.
- do...while - когда тело должно выполниться хотя бы один раз.
- for...of - для перебора элементов (чище и безопаснее).
- for...in - только для объектов, и то с hasOwnProperty.
- Никогда не используйте var в циклах - только let.
- Бесконечные циклы - проверяйте инкремент/декремент.
- Производительность - на первом месте читаемость, оптимизируйте только при реальной необходимости.
Финальный тест (проверьте себя):
javascript
// Что выведет?
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0);
}
// А это?
let i = 0;
while (i < 3) {
console.log(i);
i++;
}
// А это?
for (let i = 0; i < 10; i++) {
if (i === 5) continue;
if (i === 7) break;
console.log(i);
}
Ответы: 0 1 2 3 4 (setTimeout с let работает правильно), 0 1 2, 0 1 2 3 4 6.
Циклы - это основа алгоритмического мышления. Освоив их, вы сможете решать 90% задач по обработке данных. А освоив нюансы, перестанете бояться бесконечных циклов и научитесь писать эффективный, читаемый код. Крутите свои циклы с умом!
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
Переключатель миров: Полное руководство по switch (и почему break - ваш лучший друг)
Представьте, что вы стоите перед железнодорожным стрелочником. Вам нужно отправить поезд на один из десятка путей. Вы можете написать 10 вложенных if-else... или просто перевести один рычаг.
switch - это тот самый рычаг. Он элегантен, лаконичен и идеально подходит для ситуаций, когда одно значение сравнивается с множеством вариантов. Но, как и любой механизм, он требует понимания. Особенно его самая странная фича - "проваливание" (fallthrough).
Часть 1. Анатомия переключателя
switch сравнивает значение с вариантами (case) и выполняет код при первом совпадении.
javascript
switch (expression) {
case value1:
// код для value1
break;
case value2:
// код для value2
break;
default:
// код, если ничего не подошло
}
Простой пример:
javascript
let day = 3;
let dayName;
switch (day) {
case 1:
dayName = "Понедельник";
break;
case 2:
dayName = "Вторник";
break;
case 3:
dayName = "Среда";
break;
default:
dayName = "Неизвестный день";
}
console.log(dayName); // "Среда"
Часть 2. Сравнение - строгое! (===)
Важнейший момент: switch использует строгое сравнение (===). Типы должны совпадать!
javascript
let value = "5";
switch (value) {
case 5:
console.log("Число 5"); // НЕ сработает!
break;
case "5":
console.log("Строка 5"); // Сработает!
break;
}
Ловушка с разными типами:
javascript
let input = "1";
switch (input) {
case 1:
console.log("Число"); // Не сработает (строка !== число)
break;
case "1":
console.log("Строка"); // Сработает
break;
case true:
console.log("True"); // Не сработает
break;
}
Если нужна проверка с приведением типов: используйте if-else или приведите значение вручную перед switch.
Часть 3. break - Стоп-кран поезда
break останавливает выполнение внутри блока switch. Без него код "проваливается" в следующий case.
Что будет без break:
javascript
let color = "red";
switch (color) {
case "red":
console.log("Красный");
// нет break!
case "green":
console.log("Зеленый");
// нет break!
case "blue":
console.log("Синий");
break;
default:
console.log("Другой цвет");
}
// Вывод:
// "Красный"
// "Зеленый"
// "Синий"
Все три варианта выполнились, потому что не было break после case "red" и case "green".
Это баг или фича?
И то, и другое. Обычно отсутствие break - ошибка. Но иногда "проваливание" (fallthrough) используют специально:
javascript
let month = 4;
let days;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = 28; // или 29 в високосный год
break;
default:
days = -1;
}
console.log(days); // 30 (апрель)
Красивый паттерн: группировка нескольких case под один блок кода.
Часть 4. default - Запасной аэродром
default выполняется, если ни один case не совпал. Он необязателен, но рекомендуется.
javascript
let status = "unknown";
switch (status) {
case "active":
console.log("Пользователь активен");
break;
case "blocked":
console.log("Пользователь заблокирован");
break;
default:
console.log("Неизвестный статус"); // Сработает здесь
}
default не обязан быть последним!
В отличие от других языков, default может стоять в любом месте:
javascript
let x = 5;
switch (x) {
default:
console.log("Ничего не подошло");
break;
case 1:
console.log("Один");
break;
case 5:
console.log("Пять"); // Сработает это!
break;
}
// Вывод: "Пять" (default игнорируется, если есть совпадение)
Но не делайте так - это сбивает с толку. Всегда ставьте default в конце.
Часть 5. Выражения в case (да, это возможно!)
case может содержать не только литералы, но и выражения. Выражение вычисляется во время выполнения.
javascript
let a = 3;
let b = 2;
switch (a + b) {
case 1 + 4:
console.log("5");
break;
case 2 * 3:
console.log("6");
break;
default:
console.log("Другое");
}
// Вывод: "5"
Динамические case (но осторожно!)
javascript
let value = 10;
let threshold = 5;
switch (value) {
case threshold + 5: // case 10
console.log("Значение 10");
break;
}
⚠️ Ограничение: выражения в case вычисляются один раз при входе в switch. Они не могут содержать переменные, которые меняются внутри.
Часть 6. Подводные камни (обязательно к прочтению!)
Камень #1: Область видимости переменных
В switch одна общая область видимости для всех case. Это может привести к ошибкам:
javascript
switch (type) {
case "user":
let message = "Привет, пользователь!";
console.log(message);
break;
case "admin":
let message = "Привет, администратор!"; // Ошибка! message уже объявлена
console.log(message);
break;
}
Решение: оборачивайте case в блоки {}:
javascript
switch (type) {
case "user": {
let message = "Привет, пользователь!";
console.log(message);
break;
}
case "admin": {
let message = "Привет, администратор!"; // Теперь ок
console.log(message);
break;
}
}
Камень #2: Сравнение с объектами
switch сравнивает объекты по ссылке, а не по содержимому:
javascript
let obj1 = { id: 1 };
let obj2 = { id: 1 };
switch (obj1) {
case obj2:
console.log("Объекты равны"); // Не сработает!
break;
default:
console.log("Объекты разные"); // Сработает это
}
Решение: сравнивайте по свойству:
javascript
switch (obj1.id) {
case obj2.id:
console.log("ID совпадают");
break;
}
Камень #3: Неявное преобразование не работает
javascript
let value = "1";
switch (value) {
case 1:
console.log("Число"); // Не сработает
break;
case "1":
console.log("Строка"); // Сработает
break;
}
Часть 7. switch vs if-else - Битва эпох
Когда использовать switch:
✅ Идеально: одна переменная, много конкретных значений
javascript
switch (command) {
case "start": start(); break;
case "stop": stop(); break;
case "pause": pause(); break;
case "resume": resume(); break;
}
✅ Хорошо: группировка одинаковой логики
javascript
switch (errorCode) {
case 400:
case 401:
case 403:
handleClientError();
break;
case 500:
case 502:
case 503:
handleServerError();
break;
}
Когда использовать if-else:
✅ Лучше: диапазоны чисел
javascript
if (score >= 90) grade = "A";
else if (score >= 80) grade = "B";
else if (score >= 70) grade = "C";
✅ Лучше: сложные условия
javascript
if (user.isActive && user.age >= 18 || user.isModerator) {
grantAccess();
}
Часть 8. Продвинутые паттерны
Паттерн #1: Функции-обработчики
javascript
const handlers = {
start: () => console.log("Старт"),
stop: () => console.log("Стоп"),
pause: () => console.log("Пауза")
};
// Вместо switch:
handlers[action]?.() ?? console.log("Неизвестное действие");
Паттерн #2: switch с возвратом значения (функция)
javascript
function getStatusText(status) {
switch (status) {
case 200: return "OK";
case 201: return "Created";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 404: return "Not Found";
case 500: return "Internal Server Error";
default: return "Unknown Status";
}
}
// Заметьте: break не нужен, return выходит из функции
Паттерн #3: switch(true) - хак для сложных условий
javascript
let score = 85;
switch (true) {
case score >= 90:
grade = "A";
break;
case score >= 80:
grade = "B";
break;
case score >= 70:
grade = "C";
break;
case score >= 60:
grade = "D";
break;
default:
grade = "F";
}
console.log(grade); // "B"
Как это работает: switch(true) сравнивает true с результатом каждого case. Первый case, который вычисляется в true, срабатывает.
⚠️ Предупреждение: Это работает, но выглядит как хак. Многие разработчики предпочтут if-else. Используйте с умом и комментируйте.
Паттерн #4: switch для enum-подобных структур
javascript
const UserRole = {
ADMIN: "admin",
MODERATOR: "moderator",
USER: "user",
GUEST: "guest"
};
function getPermissions(role) {
switch (role) {
case UserRole.ADMIN:
return ["read", "write", "delete", "manage_users"];
case UserRole.MODERATOR:
return ["read", "write", "delete"];
case UserRole.USER:
return ["read", "write"];
case UserRole.GUEST:
return ["read"];
default:
return [];
}
}
Часть 9. Реальные кейсы из продакшена
Кейс #1: Обработка HTTP-статусов
javascript
async function handleResponse(response) {
switch (response.status) {
case 200:
return await response.json();
case 201:
console.log("Ресурс создан");
return await response.json();
case 204:
return null;
case 400:
throw new Error("Некорректный запрос");
case 401:
redirectToLogin();
throw new Error("Не авторизован");
case 403:
throw new Error("Доступ запрещен");
case 404:
throw new Error("Ресурс не найден");
case 500:
case 502:
case 503:
throw new Error("Ошибка сервера");
default:
throw new Error(`Неизвестная ошибка: ${response.status}`);
}
}
Кейс #2: Роутинг (простейший)
javascript
function router(pathname) {
switch (pathname) {
case "/":
case "/home":
renderHomePage();
break;
case "/about":
renderAboutPage();
break;
case "/contact":
renderContactPage();
break;
case "/products":
renderProductsPage();
break;
default:
renderNotFoundPage();
}
}
Кейс #3: Парсинг команд
javascript
function parseCommand(input) {
const parts = input.trim().split(/\s+/);
const command = parts[0].toLowerCase();
const args = parts.slice(1);
switch (command) {
case "help":
showHelp();
break;
case "echo":
console.log(args.join(" "));
break;
case "add":
const sum = args.reduce((acc, n) => acc + Number(n), 0);
console.log(`Сумма: ${sum}`);
break;
case "exit":
process.exit(0);
break;
default:
console.log(`Неизвестная команда: ${command}`);
}
}
Часть 10. Альтернативы и эволюция
Объектный lookup (современная альтернатива)
javascript
// Вместо switch
const actions = {
start: () => console.log("Старт"),
stop: () => console.log("Стоп"),
pause: () => console.log("Пауза"),
default: () => console.log("Неизвестно")
};
const execute = (action) => {
(actions[action] || actions.default)();
};
Map (ещё современнее)
javascript
const handlers = new Map([
["start", () => console.log("Старт")],
["stop", () => console.log("Стоп")],
["pause", () => console.log("Пауза")]
]);
const execute = (action) => {
handlers.get(action)?.() ?? console.log("Неизвестно");
};
Когда объект лучше switch:
- Когда обработчики - функции
- Когда нужно динамически добавлять/удалять варианты
- Когда логика простая (одна строка)
Когда switch лучше объекта:
- Когда в case сложная логика с break и проваливанием
- Когда нужна группировка нескольких значений
- Когда код читается как "выбери один из вариантов"
Итог: Манифест переключателя
- switch использует строгое сравнение (===) - помните про типы.
- break обязателен - если только вы не используете fallthrough специально.
- default ставьте в конце - для всех необработанных случаев.
- Группируйте case для одинаковой логики - это красиво.
- Оборачивайте case в {} если объявляете переменные внутри.
- Для диапазонов и сложных условий - используйте if-else или switch(true) (с комментарием).
- Для простого маппинга - рассмотрите объект или Map.
Финальный тест (проверьте себя):
javascript
let x = "2";
switch (x) {
case 2:
console.log("Число 2");
break;
case "2":
console.log("Строка 2");
case "3":
console.log("Строка 3");
break;
default:
console.log("Default");
}
// Что выведет?
Ответ: "Строка 2" и "Строка 3" (проваливание из "2" в "3", потому что нет break).
switch - это элегантный инструмент, который делает код понятнее там, где if-else превращается в лестницу в никуда. Используйте его для множества конкретных значений, помните про проваливание и не забывайте про break. И тогда ваш код будет и красивым, и предсказуемым.
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Функции - сердце JavaScript: Путь от новичка до повелителя замыканий
Функции в JavaScript - это не просто "кусочки кода, которые можно вызвать". Это граждане первого класса, путешественники между областями видимости и настоящие маги, которые могут запоминать переменные даже после того, как сами исчезли.
Добро пожаловать в мир, где функции могут быть везде: в переменных, в аргументах, в возвращаемых значениях. Мир, где замыкания - не баг, а фича.
Часть 1. Анатомия функции
Function Declaration (Объявление функции)
Самый классический способ:
javascript
function greet(name) {
return `Привет, ${name}!`;
}
console.log(greet("Анна")); // "Привет, Анна!"
Особенность: такие функции "поднимаются" (hoisting) в начало области видимости. Их можно вызывать до объявления!
javascript
sayHello("Мир"); // "Привет, Мир!" (работает!)
function sayHello(name) {
console.log(`Привет, ${name}!`);
}
Function Expression (Функциональное выражение)
Функция как значение переменной:
javascript
const greet = function(name) {
return `Привет, ${name}!`;
};
console.log(greet("Анна")); // "Привет, Анна!"
Особенность: вызывать можно только после объявления.
javascript
sayHi("Мир"); // Ошибка! sayHi не определена
const sayHi = function(name) {
console.log(`Привет, ${name}!`);
};
Стрелочные функции (Arrow functions) - современный стиль
javascript
const greet = (name) => {
return `Привет, ${name}!`;
};
// Если один параметр - скобки можно опустить
const greet = name => {
return `Привет, ${name}!`;
};
// Если тело - одно выражение - return и {} можно опустить
const greet = name => `Привет, ${name}!`;
// Без параметров - пустые скобки
const hello = () => "Привет!";
console.log(greet("Анна")); // "Привет, Анна!"
Часть 2. Параметры и аргументы: Тонкая настройка
Значения по умолчанию (Default parameters)
javascript
function greet(name = "Гость") {
console.log(`Привет, ${name}!`);
}
greet("Анна"); // "Привет, Анна!"
greet(); // "Привет, Гость!"
greet(undefined); // "Привет, Гость!" (undefined вызывает значение по умолчанию)
greet(null); // "Привет, null!" (null не вызывает значение по умолчанию)
Выражения в значениях по умолчанию
javascript
function calculate(base, multiplier = base * 2) {
return base * multiplier;
}
console.log(calculate(5)); // 5 * 10 = 50
console.log(calculate(5, 3)); // 5 * 3 = 15
Rest-параметры (сбор аргументов в массив)
javascript
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(10, 20)); // 30
Объект arguments (старый способ, не используйте)
javascript
function oldSum() {
console.log(arguments); // Псевдомассив, не умеет map/forEach
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// Не работает в стрелочных функциях!
Часть 3. Возврат значений: return
Несколько return
javascript
function checkAge(age) {
if (age < 0) {
return "Возраст не может быть отрицательным";
}
if (age < 18) {
return "Доступ запрещен";
}
return "Доступ разрешен";
}
Функция без return возвращает undefined
javascript
function doNothing() {
// ничего не возвращаем
}
console.log(doNothing()); // undefined
Ранний выход (early return)
javascript
// Плохо (глубокая вложенность)
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.hasPermission) {
// основная логика
return doWork(user);
}
}
}
return null;
}
// Хорошо (ранние возвраты)
function processUser(user) {
if (!user) return null;
if (!user.isActive) return null;
if (!user.hasPermission) return null;
// основная логика
return doWork(user);
}
Часть 4. Области видимости: Где живут переменные
Глобальная область
javascript
let globalVar = "Я везде";
function showGlobal() {
console.log(globalVar); // "Я везде"
}
Локальная область (функциональная)
javascript
function myFunction() {
let localVar = "Я только внутри";
console.log(localVar); // "Я только внутри"
}
console.log(localVar); // Ошибка! localVar не определена
Блочная область (let, const)
javascript
if (true) {
let blockVar = "Я внутри блока";
const alsoBlock = "Тоже внутри";
var functionVar = "Я везде в функции";
}
console.log(blockVar); // Ошибка!
console.log(alsoBlock); // Ошибка!
console.log(functionVar); // "Я везде в функции" (var не знает блоков)
Часть 5. Замыкания (Closures) - Магия JavaScript
Замыкание - это когда функция "запоминает" переменные из места своего создания, даже после того, как внешняя функция завершилась.
Простейший пример:
javascript
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count продолжает жить внутри counter!
Что произошло? Функция внутри createCounter "замкнула" переменную count в своей области видимости.
Реальный кейс: приватные переменные
javascript
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
return balance;
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
return balance;
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(100);
account.deposit(50); // 150
account.withdraw(30); // 120
console.log(account.getBalance()); // 120
console.log(account.balance); // undefined (приватно!)
Замыкание в циклах (классическая ловушка)
javascript
// Плохо (var)
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3 (все три!)
}, 100);
}
// Хорошо (let)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
// Хорошо (замыкание с var)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
Часть 6. Стрелочные функции vs Обычные функции
Разница №1: this
javascript
const obj = {
name: "Объект",
regularMethod: function() {
console.log(this.name); // "Объект" (this = obj)
},
arrowMethod: () => {
console.log(this.name); // undefined (this = внешний this)
}
};
obj.regularMethod(); // "Объект"
obj.arrowMethod(); // undefined
Разница №2: arguments
javascript
function regular() {
console.log(arguments); // [1, 2, 3]
}
const arrow = () => {
console.log(arguments); // Ошибка! arguments не определён
};
regular(1, 2, 3);
// arrow(1, 2, 3); // Ошибка!
Разница №3: Как конструктор
javascript
function Regular(name) {
this.name = name;
}
const Arrow = (name) => {
this.name = name;
};
const obj1 = new Regular("Анна"); // OK
// const obj2 = new Arrow("Анна"); // Ошибка! Стрелочные функции не могут быть конструкторами
Когда использовать стрелочные функции:
✅ Колбэки в массивах
javascript
[1, 2, 3].map(x => x * 2);
✅ Короткие функции без своего this
javascript
setTimeout(() => console.log("Done"), 1000);
✅ Внутри методов классов (для сохранения this)
javascript
class Button {
constructor() {
this.count = 0;
}
// Стрелка сохраняет this из конструктора
handleClick = () => {
this.count++;
console.log(this.count);
}
}
Когда НЕ использовать стрелочные функции:
❌ Как методы объекта (если нужен доступ к this объекта)
javascript
const obj = {
value: 42,
getValue: () => this.value // не работает!
};
❌ Как конструкторы
javascript
const Person = (name) => { this.name = name; };
// const p = new Person("Анна"); // Ошибка!
Часть 7. IIFE (Immediately Invoked Function Expression)
Функция, которая выполняется сразу после объявления.
javascript
// Классический IIFE
(function() {
console.log("Я выполнился сразу!");
})();
// Со стрелками
(() => {
console.log("Тоже сразу");
})();
// С параметрами
(function(name) {
console.log(`Привет, ${name}!`);
})("Анна");
Зачем это нужно? Создать изолированную область видимости (до появления let и const).
javascript
// Старый паттерн для модулей
const myModule = (function() {
let privateVar = "Я приватный";
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
myModule.publicMethod(); // "Я приватный"
myModule.privateVar; // undefined
Часть 8. Функции высшего порядка
Функции, которые принимают другие функции или возвращают их.
map, filter, reduce - классика
javascript
const numbers = [1, 2, 3, 4, 5];
// map - преобразование
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter - фильтрация
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce - агрегация
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
Создание своей функции высшего порядка
javascript
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Композиция функций
javascript
const compose = (f, g) => x => f(g(x));
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const add1ThenMultiply2 = compose(multiply2, add1);
console.log(add1ThenMultiply2(5)); // (5 + 1) * 2 = 12
Часть 9. Рекурсия - функция вызывает саму себя
javascript
// Классика: факториал
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
Обход дерева (DOM)
javascript
function getAllTextNodes(element) {
let texts = [];
if (element.nodeType === Node.TEXT_NODE && element.textContent.trim()) {
texts.push(element.textContent);
}
for (let child of element.childNodes) {
texts = texts.concat(getAllTextNodes(child));
}
return texts;
}
// Использование
const textNodes = getAllTextNodes(document.body);
Опасность рекурсии: переполнение стека
javascript
function infinite() {
return infinite(); // RangeError: Maximum call stack size exceeded
}
Решение: хвостовая рекурсия (оптимизируется не во всех движках)
javascript
function factorialTail(n, acc = 1) {
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc);
}
Часть 10. Параметры: передача по значению и по ссылке
Примитивы передаются по значению
javascript
function changePrimitive(x) {
x = 100;
console.log("Внутри:", x); // 100
}
let a = 10;
changePrimitive(a);
console.log("Снаружи:", a); // 10 (не изменилось!)
Объекты передаются по ссылке
javascript
function changeObject(obj) {
obj.name = "Изменено";
console.log("Внутри:", obj.name); // "Изменено"
}
let user = { name: "Анна" };
changeObject(user);
console.log("Снаружи:", user.name); // "Изменено" (изменилось!)
Как скопировать объект
javascript
// Поверхностная копия
const copy1 = { ...original };
const copy2 = Object.assign({}, original);
// Глубокая копия (простой способ)
const deepCopy = JSON.parse(JSON.stringify(original));
// Внимание! Не работает с функциями, Date, undefined, циклическими ссылками
Часть 11. Чистые функции (Pure functions)
Чистая функция:
- Одинаковый результат для одинаковых аргументов
- Не имеет побочных эффектов (не меняет внешние переменные)
javascript
// Чистая функция
function add(a, b) {
return a + b;
}
// Нечистая функция (зависит от внешней переменной)
let tax = 0.2;
function addWithTax(price) {
return price + price * tax; // tax может измениться снаружи
}
// Нечистая функция (меняет внешний массив)
function addToArray(arr, value) {
arr.push(value);
return arr;
}
// Чистая версия
function addToArrayPure(arr, value) {
return [...arr, value];
}
Часть 12. Реальные паттерны из продакшена
Паттерн #1: Мемоизация (кэширование результатов)
javascript
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Из кэша");
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const slowFibonacci = function(n) {
if (n <= 1) return n;
return slowFibonacci(n - 1) + slowFibonacci(n - 2);
};
const fastFibonacci = memoize(slowFibonacci);
console.log(fastFibonacci(40)); // быстро!
Паттерн #2: Каррирование (Currying)
javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...more) => curried(...args, ...more);
};
}
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
Паттерн #3: Декоратор логирования
javascript
function withLogging(fn) {
return function(...args) {
console.log(`Вызов ${fn.name} с аргументами:`, args);
const start = performance.now();
const result = fn(...args);
const end = performance.now();
console.log(`Результат: ${result}, время: ${end - start}ms`);
return result;
};
}
const expensiveCalc = withLogging(function(n) {
let sum = 0;
for (let i = 0; i < n; i++) sum += i;
return sum;
});
expensiveCalc(1000000);
// Вывод:
// Вызов expensiveCalc с аргументами: [1000000]
// Результат: 499999500000, время: 2.345ms
Паттерн #4: Фабрика функций
javascript
function createValidator(type) {
switch (type) {
case "email":
return value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
case "phone":
return value => /^\+?[0-9]{10,15}$/.test(value);
case "zipcode":
return value => /^[0-9]{5,6}$/.test(value);
default:
return value => true;
}
}
const validateEmail = createValidator("email");
console.log(validateEmail("test@example.com")); // true
console.log(validateEmail("invalid")); // false
Часть 13. Подводные камни
Камень #1: Отсутствие return = undefined
javascript
function getValue() {
const result = 42;
// забыли return
}
console.log(getValue()); // undefined
Камень #2: Именование функций в выражениях
javascript
const factorial = function fac(n) {
return n <= 1 ? 1 : n * fac(n - 1);
};
// Имя fac доступно только внутри функции
Камень #3: this в колбэках
javascript
const obj = {
name: "Объект",
methods: {
regular: function() { console.log(this.name); },
arrow: () => console.log(this.name)
}
};
obj.methods.regular(); // undefined (this = methods)
obj.methods.arrow(); // undefined (this = внешний)
Итог: Манифест функций
- Function Declaration - для глобальных функций, которые можно вызывать до объявления.
- Function Expression - когда нужна функция в переменной.
- Стрелочные функции - для коротких колбэков и сохранения this.
- Значения по умолчанию - всегда указывайте для необязательных параметров.
- Rest-параметры вместо arguments.
- Ранние возвраты (early return) - избавляют от вложенности.
- Замыкания - мощный инструмент для инкапсуляции.
- Чистые функции - легче тестировать и отлаживать.
Финальный тест (проверьте себя):
javascript
// Что выведет?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// А это?
const obj = {
name: "JS",
getName: () => this.name
};
console.log(obj.getName());
// И это?
function create() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count
};
}
const counter = create();
console.log(counter.increment());
console.log(counter.increment());
console.log(counter.decrement());
Ответы: 3, 3, 3 (var не имеет блочной области), undefined (стрелка берет this из глобального контекста), 1, 2, 1.
Функции - это душа JavaScript. Они могут быть простыми и понятными, а могут - источником магии и мощи. Освойте их, и вы сможете выразить любую логику на этом прекрасном языке. Помните: каждая функция — это маленькая вселенная со своими правилами, переменными и секретами. Изучайте их, и они откроют вам свои тайны.
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Анонимные граждане: Почему Function Expression изменит ваше представление о функциях
В мире JavaScript функции - это граждане первого класса. Их можно передавать, присваивать, возвращать и... даже создавать без имени. Но есть два способа создать функцию: декларация (Function Declaration) и выражение (Function Expression). Они похожи, как близнецы, но с разными судьбами.
Function Expression - это функция, которая рождается как значение. Она не существует до тех пор, пока поток выполнения до нее не дойдет. И это меняет всё.
Часть 1. Что такое Function Expression?
Function Expression создает функцию как часть выражения (обычно присваивания).
javascript
// Это Function Expression
const greet = function(name) {
return `Привет, ${name}!`;
};
console.log(greet("Анна")); // "Привет, Анна!"
Ключевое отличие от Function Declaration: у выражения нет имени (или оно может быть). Функция справа от = - анонимная.
А вот так не работает:
javascript
// Function Declaration (не выражение)
function greet(name) {
return `Привет, ${name}!`;
}
Часть 2. Function Expression vs Function Declaration - Битва за существование
Отличие №1: Hoisting (поднятие)
Самое важное различие. Function Declaration "поднимаются" в начало области видимости. Function Expression - нет.
javascript
// Function Declaration - работает!
sayHello("Мир"); // "Привет, Мир!"
function sayHello(name) {
console.log(`Привет, ${name}!`);
}
javascript
// Function Expression - НЕ работает!
sayHi("Мир"); // Ошибка! Cannot access 'sayHi' before initialization
const sayHi = function(name) {
console.log(`Привет, ${name}!`);
};
Почему? При создании переменной через const/let происходит Temporal Dead Zone - доступ к переменной есть только после строки объявления.
Отличие №2: Имя функции
Function Declaration всегда имеет имя. Function Expression может быть анонимным или именованным.
javascript
// Анонимное Function Expression
const anonymous = function() {
console.log("У меня нет имени");
};
// Именованное Function Expression
const named = function myFunction() {
console.log("Меня зовут myFunction");
};
Зачем давать имя Function Expression?
- Для рекурсии:
javascript
const factorial = function f(n) {
if (n <= 1) return 1;
return n * f(n - 1); // f доступно внутри
};
// Внешнее имя factorial может быть перезаписано, но рекурсия продолжит работать
- Для отладки (стек трейс):
javascript
// В консоли ошибки увидите "myFunc", а не "anonymous"
const processData = function myFunc(data) {
throw new Error("Ошибка!");
};
Отличие №3: Когда создаются
javascript
// Function Declaration создается при парсинге кода
// Function Expression создается во время выполнения
console.log(typeof declaration); // "function" (уже существует)
console.log(typeof expression); // "undefined" (еще нет)
function declaration() {}
const expression = function() {};
Часть 3. Анонимные функции - граждане без паспорта
Анонимные функции - это функции без имени. Они невероятно полезны для коротких операций.
javascript
// Самый частый случай - колбэки
[1, 2, 3].forEach(function(item) {
console.log(item * 2);
});
// Сразу после создания (IIFE)
(function() {
console.log("Я выполнился сразу и без имени!");
})();
// Как аргумент другой функции
setTimeout(function() {
console.log("Прошла секунда");
}, 1000);
Проблема анонимных функций: их сложно отлаживать.
javascript
// Плохо (в стеке будет "anonymous")
const result = [1, 2, 3].map(function(x) {
return x * 2;
});
// Хорошо (именованное выражение)
const result = [1, 2, 3].map(function double(x) {
return x * 2;
});
Часть 4. Named Function Expression (NFE) - Тайное оружие
Named Function Expression - это выражение, у которого есть имя, но оно не создает переменную в внешней области видимости.
javascript
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(5)); // 120
console.log(typeof fact); // "undefined" (имя недоступно снаружи!)
Суперсила NFE: имя доступно только внутри функции.
Реальный кейс - защита от перезаписи переменной
javascript
let fibonacci = function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
};
// Кто-то перезаписал переменную
const oldFibonacci = fibonacci;
fibonacci = function() { return 0; };
// Старая функция продолжает работать через oldFibonacci
console.log(oldFibonacci(10)); // 55 (рекурсия через fib, а не через fibonacci!)
Если бы мы использовали анонимную функцию:
javascript
let fibonacci = function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2); // зависит от fibonacci
};
fibonacci = function() { return 0; };
// Теперь рекурсия сломана - вызывает новую функцию
Часть 5. Function Expression в действии
Сценарий #1: Колбэки (самый частый)
javascript
// Добавление обработчика событий
button.addEventListener("click", function() {
console.log("Кнопка нажата!");
});
// Асинхронные операции
fetch("/api/data")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
});
Сценарий #2: Функции высшего порядка
javascript
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Сценарий #3: Мемоизация (кэширование)
javascript
function memoize(fn) {
const cache = {};
return function(arg) {
if (arg in cache) {
console.log("Из кэша");
return cache[arg];
}
const result = fn(arg);
cache[arg] = result;
return result;
};
}
const slowSquare = function(n) {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += n;
}
return result;
};
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // вычисляет
console.log(fastSquare(5)); // из кэша
Сценарий #4: Замыкания и приватные переменные
javascript
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getValue: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getValue()); // 2
Часть 6. IIFE (Immediately Invoked Function Expression) - Классика жанра
IIFE - это Function Expression, которое выполняется сразу после создания.
javascript
// Классический синтаксис
(function() {
console.log("Выполнилось немедленно!");
})();
// Со стрелочными функциями
(() => {
console.log("Тоже немедленно");
})();
// С параметрами
(function(name) {
console.log(`Привет, ${name}!`);
})("Анна");
// С возвращаемым значением
const result = (function(a, b) {
return a + b;
})(5, 3);
console.log(result); // 8
Зачем нужен IIFE сегодня?
- Создание изолированной области видимости (до ES6):
javascript
// До появления let/const
(function() {
var privateVar = "Я не виден снаружи";
// код, который не засоряет глобальную область
})();
console.log(typeof privateVar); // "undefined"
- Модульный паттерн (старый способ):
javascript
const myModule = (function() {
let privateData = "секрет";
function privateMethod() {
console.log(privateData);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
myModule.publicMethod(); // "секрет"
- Фиксация значений в циклах (старый способ):
javascript
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2 (а не 3,3,3)
}, 100);
})(i);
}
Сейчас это решается через let, но IIFE остается в legacy-коде.
Часть 7. Function Expression в массивах и объектах
Функции - это значения, поэтому их можно хранить в структурах данных.
Массив функций
javascript
const operations = [
function(x) { return x + x; }, // 0: удвоение
function(x) { return x * x; }, // 1: квадрат
function(x) { return x > 10; }, // 2: больше 10?
function(x) { return -x; } // 3: отрицание
];
console.log(operations[0](5)); // 10
console.log(operations[1](5)); // 25
console.log(operations[2](15)); // true
console.log(operations[3](5)); // -5
Объект с методами
javascript
const calculator = {
add: function(a, b) { return a + b; },
subtract: function(a, b) { return a - b; },
multiply: function(a, b) { return a * b; },
divide: function(a, b) { return a / b; }
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.divide(10, 2)); // 5
Динамическое создание методов
javascript
const actions = {};
const actionNames = ["start", "stop", "pause", "resume"];
actionNames.forEach(function(name) {
actions[name] = function() {
console.log(`Выполняется действие: ${name}`);
};
});
actions.start(); // "Выполняется действие: start"
actions.pause(); // "Выполняется действие: pause"
Часть 8. Function Expression vs Arrow Functions
Стрелочные функции - это тоже Function Expression, но с важными отличиями.
javascript
// Обычное Function Expression
const regular = function(x) {
return x * 2;
};
// Стрелочное Function Expression
const arrow = (x) => x * 2;
Ключевые отличия:
javascript
// this - главное отличие
const obj = {
name: "Объект",
regularFunc: function() {
console.log(this.name); // "Объект"
},
arrowFunc: () => {
console.log(this.name); // undefined (this из глобального контекста)
}
};
obj.regularFunc();
obj.arrowFunc();
Часть 9. Подводные камни Function Expression
Камень #1: Забытый return
javascript
// Возвращает undefined
const double = function(x) {
x * 2; // забыл return
};
console.log(double(5)); // undefined
Камень #2: Отсутствие точки с запятой
javascript
// Так работает
const fn1 = function() { return 1; };
const fn2 = function() { return 2; };
// А так - ошибка
const fn3 = function() { return 3; }
(function() { console.log("IIFE"); })(); // Ошибка! JS думает, что функция продолжается
Правило: всегда ставьте ; после Function Expression, особенно перед IIFE.
Камень #3: Имя функции не перезаписывает переменную
javascript
const myFunc = function test() {
console.log(typeof test); // "function"
console.log(typeof myFunc); // "function"
};
myFunc = 42; // перезаписали переменную
// Теперь myFunc - число 42
console.log(typeof myFunc); // "number"
// Но внутри функции test продолжает существовать (но вызвать нельзя)
// test(); // ReferenceError! test не доступна снаружи
Камень #4: Рекурсия через переменную
javascript
// Опасно - переменная может быть перезаписана
let countdown = function(n) {
if (n <= 0) return;
console.log(n);
countdown(n - 1); // зависит от переменной countdown
};
const original = countdown;
countdown = function() { console.log("Ха-ха!"); };
original(3);
// Выведет: 3, 2, 1 (рекурсия работает через original!)
// Но если бы внутри было countdown, сломалась бы
// Безопасно - именованное выражение
let safeCountdown = function countdown(n) {
if (n <= 0) return;
console.log(n);
countdown(n - 1); // использует имя countdown, а не переменную
};
Часть 10. Реальные паттерны из продакшена
Паттерн #1: Условное создание функций
javascript
let parser;
if (typeof window !== "undefined") {
// Браузер
parser = function(str) {
return new DOMParser().parseFromString(str, "text/html");
};
} else {
// Node.js
const { JSDOM } = require("jsdom");
parser = function(str) {
return new JSDOM(str).window.document;
};
}
// Используем единый интерфейс
const doc = parser("<div>Hello</div>");
Паттерн #2: Функции с памятью (статическая переменная)
javascript
const uniqueId = function() {
let counter = 0;
return function() {
counter++;
return counter;
};
}(); // IIFE!
console.log(uniqueId()); // 1
console.log(uniqueId()); // 2
console.log(uniqueId()); // 3
Паттерн #3: Throttle (ограничение частоты вызовов)
javascript
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func(...args);
}
};
}
const throttledLog = throttle(function(msg) {
console.log(msg, Date.now());
}, 1000);
throttledLog("Раз"); // выполнится
throttledLog("Два"); // проигнорируется
setTimeout(() => throttledLog("Три"), 1100); // выполнится
Паттерн #4: Once (выполнить функцию только один раз)
javascript
function once(func) {
let executed = false;
let result;
return function(...args) {
if (!executed) {
executed = true;
result = func(...args);
}
return result;
};
}
const initialize = once(function() {
console.log("Инициализация...");
return { status: "ready" };
});
console.log(initialize()); // "Инициализация..." → { status: "ready" }
console.log(initialize()); // { status: "ready" } (без повторной инициализации)
Паттерн #5: Частичное применение (Partial application)
javascript
function partial(func, ...fixedArgs) {
return function(...remainingArgs) {
return func(...fixedArgs, ...remainingArgs);
};
}
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
const sayHello = partial(greet, "Привет");
const sayHelloToAnna = partial(sayHello, "Анна");
console.log(sayHelloToAnna("!")); // "Привет, Анна!"
Часть 11. Производительность: есть ли разница?
Споры о производительности разных способов создания функций обычно бессмысленны для 99% приложений.
javascript
// Function Declaration
function declared() { return 1; }
// Function Expression
const expressed = function() { return 1; };
// Arrow Function
const arrowed = () => 1;
Факты:
- Разница в наносекундах
- Современные движки оптимизируют всё
- Выбирайте по читаемости, а не по скорости
Исключение: создание тысяч функций в цикле - тогда Function Expression может быть чуть медленнее из-за создания новых замыканий.
Итог: Манифест Function Expression
- Function Expression не поднимается - вызывайте только после объявления.
- Анонимные функции - хороши для коротких колбэков, плохи для отладки.
- Named Function Expression - используйте для рекурсии и защиты от перезаписи.
- IIFE - классика, но сегодня редко нужен (есть let и модули).
- Function Expression в массивах/объектах - мощный паттерн для динамического кода.
- Всегда ставьте ; после Function Expression - защита от ошибок с IIFE.
- Для this - обычное выражение дает свой this, стрелка - лексический.
Финальный тест (проверьте себя):
javascript
// Что выведет?
const a = function() { return 1; };
function b() { return 2; }
console.log(typeof a); // ?
console.log(typeof b); // ?
// А это?
const factorial = function f(n) {
if (n <= 1) return 1;
return n * f(n - 1);
};
const copy = factorial;
factorial = function() { return 0; };
console.log(copy(5)); // ?
// И это?
(function() {
var x = 10;
})();
console.log(typeof x); // ?
Ответы: "function", "function", 120 (рекурсия через f, не через factorial), "undefined" (IIFE создал локальную область).
Function Expression - это не просто альтернативный синтаксис. Это философия: функция как значение, которое можно создавать, передавать и использовать в нужный момент. Освойте этот подход, и ваш код станет гибче, выразительнее и ближе к функциональному программированию. А JavaScript - это прежде всего функции. Всегда помните об этом.
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
Стрелы в цель: Почему стрелочные функции изменили JavaScript навсегда
В 2015 году в мир JavaScript пришли стрелочные функции. И всё изменилось. Код стал короче, this перестал быть проклятием, а разработчики наконец-то вздохнули с облегчением.
Но стрелочные функции - это не просто "красивая запись". Это совершенно другой подход к функциям. Они короче, да. Они лаконичнее, да. Но главное - у них нет своего this. И эта "неполноценность" оказалась их главной суперсилой.
Часть 1. Рождение стрелки: От многословия к лаконичности
Посмотрите на обычную функцию:
javascript
// Старый мир (много букв)
const multiply = function(a, b) {
return a * b;
};
А теперь на стрелочную:
javascript
// Новый мир (только суть)
const multiply = (a, b) => {
return a * b;
};
Разница небольшая. Но стрелки умеют сжиматься до неузнаваемости:
javascript
// Если один параметр - скобки можно опустить
const double = x => {
return x * 2;
};
// Если тело - одно выражение - можно опустить return и {}
const double = x => x * 2;
// Если вообще нет параметров - нужны пустые скобки
const hello = () => console.log("Привет!");
Эволюция одной функции:
javascript
// Способ 1: классическое объявление
function square(x) {
return x * x;
}
// Способ 2: Function Expression
const square = function(x) {
return x * x;
};
// Способ 3: стрелочная (многострочная)
const square = (x) => {
return x * x;
};
// Способ 4: стрелочная (максимально короткая)
const square = x => x * x;
Часть 2. Синтаксис от А до Я
Базовые формы
javascript
// 1. Без параметров
const getTime = () => Date.now();
// 2. С одним параметром (скобки необязательны)
const double = x => x * 2;
// 3. С несколькими параметрами (скобки обязательны)
const add = (a, b) => a + b;
// 4. С телом из нескольких выражений (нужны {} и return)
const process = (a, b) => {
const sum = a + b;
const product = a * b;
return { sum, product };
};
Возврат объектов (хитрый момент)
Объект тоже нужно обернуть в скобки, иначе JS подумает, что это блок кода:
javascript
// ❌ Не работает ({} воспринимается как блок)
const getUser = () => { name: "Анна", age: 25 };
// ✅ Правильно
const getUser = () => ({ name: "Анна", age: 25 });
// ✅ Или с явным return
const getUser = () => {
return { name: "Анна", age: 25 };
};
Возврат массива (без проблем)
javascript
const getNumbers = () => [1, 2, 3, 4, 5];
Часть 3. Главная магия: Стрелки и this
Это самый важный раздел. Запомните: у стрелочных функций нет своего this. Они берут this из внешнего контекста (лексическое связывание).
Проблема обычных функций:
javascript
const user = {
name: "Анна",
greet: function() {
setTimeout(function() {
console.log(`Привет, ${this.name}`); // undefined!
}, 1000);
}
};
user.greet(); // "Привет, undefined"
Что произошло? Внутри setTimeout своя функция, и её this - это глобальный объект (или undefined в строгом режиме).
Решение со стрелкой:
javascript
const user = {
name: "Анна",
greet: function() {
setTimeout(() => {
console.log(`Привет, ${this.name}`); // "Привет, Анна"
}, 1000);
}
};
user.greet(); // "Привет, Анна"
Стрелка взяла this из внешней функции greet, где this - это объект user.
Ещё пример - обработчик событий:
javascript
// Проблема с обычной функцией
button.addEventListener("click", function() {
console.log(this); // this = button (хорошо)
setTimeout(function() {
console.log(this); // this = window (плохо!)
}, 100);
});
// Решение со стрелкой
button.addEventListener("click", function() {
setTimeout(() => {
console.log(this); // this = button (сохранился!)
}, 100);
});
Стрелка как метод объекта - НЕ ДЕЛАЙТЕ ТАК:
javascript
const obj = {
name: "Объект",
arrowMethod: () => {
console.log(this.name); // undefined!
}
};
obj.arrowMethod(); // undefined
// Потому что this у стрелки - это this внешнего контекста (глобальный)
Правило: Используйте обычные функции для методов объектов, стрелки - для колбэков.
Часть 4. Нет arguments - и это проблема
У стрелочных функций нет собственного псевдомассива arguments.
javascript
// Обычная функция - есть
function regular() {
console.log(arguments); // [1, 2, 3]
}
regular(1, 2, 3);
// Стрелка - нет
const arrow = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};
arrow(1, 2, 3);
Что делать? Используйте rest-параметры:
javascript
const sum = (...numbers) => {
return numbers.reduce((acc, n) => acc + n, 0);
};
console.log(sum(1, 2, 3, 4, 5)); // 15
Часть 5. Нельзя использовать как конструктор
Стрелочные функции нельзя вызывать с new. У них нет свойства prototype.
javascript
const Person = (name) => {
this.name = name;
};
// const user = new Person("Анна"); // TypeError! Person is not a constructor
// Только обычные функции
function RegularPerson(name) {
this.name = name;
}
const user = new RegularPerson("Анна"); // ✅
Часть 6. Нет prototype
У стрелочных функций нет свойства prototype. Они легче и проще.
javascript
function regular() {}
const arrow = () => {};
console.log(regular.prototype); // { constructor: regular }
console.log(arrow.prototype); // undefined
Часть 7. Когда использовать стрелочные функции
✅ Идеальные сценарии:
1. Короткие колбэки для массивов:
javascript
// Вместо этого
const doubled = numbers.map(function(n) {
return n * 2;
});
// Пишем это
const doubled = numbers.map(n => n * 2);
2. Цепочки операций:
javascript
const result = orders
.filter(order => order.status === "paid")
.map(order => order.total)
.reduce((sum, total) => sum + total, 0);
3. Промисы и асинхронный код:
javascript
fetch("/api/user")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
4. setTimeout / setInterval:
javascript
setTimeout(() => {
console.log("Прошла секунда");
}, 1000);
5. Колбэки в React/компонентах:
jsx
<button onClick={() => this.handleClick()}>
Нажми меня
</button>
❌ Плохие сценарии:
1. Методы объектов:
javascript
// Плохо
const counter = {
count: 0,
increment: () => {
this.count++; // this — не counter!
}
};
// Хорошо
const counter = {
count: 0,
increment() {
this.count++;
}
};
2. Конструкторы:
javascript
// Плохо
const Widget = (width, height) => {
this.width = width; // не работает
};
// Хорошо
class Widget {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
3. Функции, которым нужен свой arguments:
javascript
// Плохо
const debug = (...args) => {
console.log(arguments); // не работает
};
// Хорошо
function debug() {
console.log(arguments);
}
4. Генераторы:
javascript
// Стрелки не могут быть генераторами
function* generator() {
yield 1;
yield 2;
}
Часть 8. Стрелки в классах - современный паттерн
Стрелочные функции в классах решают проблему потери this в методах:
javascript
class Button {
constructor(label) {
this.label = label;
}
// Обычный метод (теряет this при передаче)
handleClick() {
console.log(`Нажата кнопка: ${this.label}`);
}
// Стрелочный метод (сохраняет this)
handleClickArrow = () => {
console.log(`Нажата кнопка: ${this.label}`);
}
}
const btn = new Button("Отправить");
// Проблема: this потерян
setTimeout(btn.handleClick, 1000); // "Нажата кнопка: undefined"
// Решение: стрелка сохраняет контекст
setTimeout(btn.handleClickArrow, 1000); // "Нажата кнопка: Отправить"
Часть 9. Стрелки и деструктуризация - идеальная пара
javascript
const users = [
{ name: "Анна", age: 25 },
{ name: "Борис", age: 30 },
{ name: "Вика", age: 28 }
];
// Красиво и читаемо
const names = users.map(({ name }) => name);
const adults = users.filter(({ age }) => age >= 18);
const totalAge = users.reduce((sum, { age }) => sum + age, 0);
Часть 10. Реальные паттерны из продакшена
Паттерн #1: Композиция функций
javascript
const compose = (f, g) => x => f(g(x));
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const add1ThenMultiply2 = compose(multiply2, add1);
console.log(add1ThenMultiply2(5)); // (5 + 1) * 2 = 12
Паттерн #2: Каррирование (простое)
javascript
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Паттерн #3: Частичное применение
javascript
const fetchAPI = endpoint => params =>
fetch(`/api/${endpoint}?${new URLSearchParams(params)}`)
.then(res => res.json());
const fetchUsers = fetchAPI("users");
const fetchProducts = fetchAPI("products");
fetchUsers({ limit: 10 }).then(users => console.log(users));
Паттерн #4: Условный рендеринг в JSX
jsx
const UserGreeting = ({ user, isLoggedIn }) => (
<div>
{isLoggedIn ? (
<h1>С возвращением, {user.name}!</h1>
) : (
<button onClick={() => login()}>Войти</button>
)}
</div>
);
Часть 11. Подводные камни
Камень #1: Возврат объекта без скобок
javascript
// ❌
const getUser = () => { name: "Анна" };
console.log(getUser()); // undefined
// ✅
const getUser = () => ({ name: "Анна" });
Камень #2: Стрелка в конструкторе класса
javascript
class Example {
constructor() {
this.value = 42;
// Стрелка в конструкторе - норм
setTimeout(() => {
console.log(this.value); // 42
}, 100);
}
}
Камень #3: Нечитаемая цепочка
javascript
// Плохо (слишком много стрелок подряд)
const result = data.map(x => x.items).filter(x => x.active).map(x => x.price).reduce((a, b) => a + b, 0);
// Хорошо (разбито на строки)
const result = data
.map(x => x.items)
.filter(x => x.active)
.map(x => x.price)
.reduce((sum, price) => sum + price, 0);
Камень #4: Слишком умная однострочка
javascript
// Плохо (непонятно)
const validate = user => user && user.age >= 18 && user.isActive || user.isAdmin;
// Хорошо (понятно)
const validate = user => {
if (!user) return false;
if (user.isAdmin) return true;
return user.age >= 18 && user.isActive;
};
Часть 12. Производительность: мифы и факты
Миф: Стрелочные функции медленнее обычных.
Факт: Разница настолько мала, что вы её никогда не заметите. Современные движки оптимизируют стрелки отлично.
javascript
// Измерять бессмысленно - разница в наносекундах
console.time("regular");
for (let i = 0; i < 10000000; i++) {
(function(x) { return x * 2; })(i);
}
console.timeEnd("regular");
console.time("arrow");
for (let i = 0; i < 10000000; i++) {
(x => x * 2)(i);
}
console.timeEnd("arrow");
Выбирайте по читаемости, а не по скорости.
Итог: Манифест стрелочных функций
- Короткий синтаксис - пишите меньше, делайте больше.
- Нет своего this - берут из внешнего контекста (это суперсила для колбэков).
- Не использовать как методы объектов - для методов нужны обычные функции.
- Не использовать как конструкторы - для классов есть class.
- Нет arguments - используйте ...rest.
- Идеальны для колбэков - map, filter, reduce, setTimeout, промисы.
- Читаемость важнее краткости - не жертвуйте понятностью ради одной строки.
Финальный тест (проверьте себя):
javascript
// Что выведет?
const obj = {
name: "JS",
getName: () => this.name
};
console.log(obj.getName());
// А это?
const calculator = {
value: 10,
double: () => this.value * 2,
triple() { return this.value * 3; }
};
console.log(calculator.double());
console.log(calculator.triple());
// И это?
const add = x => y => x + y;
const add5 = add(5);
console.log(add5(3));
Ответы: undefined (стрелка берет глобальный this), NaN (this.value - undefined → NaN), 30, 8.
Стрелочные функции - это не просто синтаксический сахар. Это новый способ думать о функциях в JavaScript. Они делают код чище, избавляют от головной боли с this и открывают двери в мир функционального программирования. Используйте их мудро, и ваш код станет красивее, понятнее и современнее. Стреляйте в цель!
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Дзен и искусство JavaScript: Почему typeof null === "object" - это нормально (почти)
JavaScript странный. Это знают все. Но за каждой странностью стоит история, компромисс или глубокое архитектурное решение. JavaScript не пытается быть идеальным. Он пытается быть полезным. И иногда его "полезность" выглядит как безумие.
Добро пожаловать в мир, где массивы - это объекты, NaN не равен сам себе, а 0.1 + 0.2 - это повод для философского спора.
Часть 1. Система типов: Хаос со смыслом
1.1 typeof null === "object" - Древний баг, ставший традицией
Это самый известный баг в истории программирования.
javascript
typeof null; // "object" (должно быть "null")
Почему так произошло? В первой версии JavaScript значения хранились с тегами типа:
- 000 - объект
- 001 - число
- 010 - строка
- 100 - булево
- 110 - undefined
null представлял собой нулевой указатель (0x00), что в этой системе попадало в категорию "объект".
Можно ли это исправить? Нет. Слишком много сайтов полагаются на это поведение. Баг стал фичей.
Как правильно проверять на null:
javascript
const isNull = value => value === null;
1.2 NaN !== NaN - Одинокое число
javascript
NaN === NaN; // false
NaN == NaN; // false
NaN (Not a Number) - это результат некорректной математической операции. Оно обозначает "неопределенный результат", и два неопределенных результата не могут быть равны.
javascript
0 / 0; // NaN
Math.sqrt(-1); // NaN
parseInt("привет"); // NaN
// Как проверять
Number.isNaN(NaN); // true
isNaN(NaN); // true (но есть нюанс)
isNaN("привет"); // true (сначала приводит к числу!)
Number.isNaN("привет"); // false (строго)
1.3 0 === -0 но 1 / 0 !== 1 / -0
javascript
0 === -0; // true (формально равны)
1 / 0; // Infinity
1 / -0; // -Infinity
Object.is(0, -0); // false (различает)
Зачем нужен -0? Для определенных математических операций и направления движения (например, в анимациях).
Часть 2. Приведения типов: Искусство неявных превращений
2.1 Сложение против вычитания
javascript
"5" + 3; // "53" (плюс любит строки)
"5" - 3; // 2 (минус любит числа)
"5" * "3"; // 15 (умножение любит числа)
"a" - 1; // NaN
2.2 Магическое сложение массивов и объектов
javascript
[] + []; // "" (пустая строка)
[] + {}; // "[object Object]"
{} + []; // 0 (сюрприз! {} воспринимается как блок кода)
({}) + []; // "[object Object]"
[1, 2] + [3, 4]; // "1,23,4" (массивы → строки → конкатенация)
2.3 Правда и ложь: 8 врагов
Только 8 значений приводятся к false. Всё остальное - true.
javascript
// Falsy-значения
false
0
-0
0n // BigInt ноль
""
null
undefined
NaN
// Truthy-сюрпризы
Boolean([]); // true (пустой массив!)
Boolean({}); // true (пустой объект!)
Boolean("false"); // true (непустая строка!)
Boolean(new Boolean(false)); // true (объект всегда true)
Часть 3. Функции: Странности на каждом шагу
3.1 Hoisting - Поднятие переменных
javascript
console.log(a); // undefined (не ReferenceError!)
var a = 5;
// Как это видит движок:
var a;
console.log(a);
a = 5;
С let и const всё иначе - Temporal Dead Zone:
javascript
console.log(b); // ReferenceError: Cannot access before initialization
let b = 5;
3.2 Function Declaration vs Expression
javascript
// Работает (hoisting функции)
sayHello(); // "Hello"
function sayHello() {
console.log("Hello");
}
// Не работает (hoisting переменной, но не значения)
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi");
};
3.3 Возврат без return
javascript
function noReturn() {}
noReturn(); // undefined
// Стрелочный однострочник
const double = x => x * 2; // неявный return
// Но с объектом - ловушка
const getUser = () => { name: "Анна" }; // undefined
const getCorrectUser = () => ({ name: "Анна" }); // ✅
Часть 4. Объекты: Иллюзия простоты
4.1 Ключи - всегда строки
javascript
const obj = {};
obj[true] = "правда";
obj[1] = "единица";
obj[{}] = "объект";
console.log(obj); // { "1": "единица", "true": "правда", "[object Object]": "объект" }
4.2 Копирование по ссылке
javascript
const user1 = { name: "Анна" };
const user2 = user1;
user2.name = "Борис";
console.log(user1.name); // "Борис" (изменилось!)
// Копирование
const copy = { ...user1 }; // поверхностное
const deepCopy = JSON.parse(JSON.stringify(user1)); // глубокое (с ограничениями)
4.3 Массивы - это объекты
javascript
const arr = [1, 2, 3];
typeof arr; // "object"
arr instanceof Array; // true
arr.test = "свойство";
console.log(arr.test); // "свойство" (массив с пользовательским свойством)
console.log(arr.length); // 3 (длина не изменилась)
Часть 5. Числа: Танцы с плавающей точкой
5.1 Проблема точности
javascript
0.1 + 0.2 === 0.3; // false
0.1 + 0.2; // 0.30000000000000004
// Решение
const epsilon = 0.0000001;
Math.abs((0.1 + 0.2) - 0.3) < epsilon; // true
5.2 Максимальное целое
javascript
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true (сюрприз!)
// Решение для больших чисел
BigInt(9007199254740991) + 1n; // 9007199254740992n
Часть 6. Глобальный объект: Окно в мир
6.1 Забытые var становятся глобальными
javascript
function leak() {
accidentallyGlobal = "я глобальный"; // забыли var/let/const
}
leak();
console.log(window.accidentallyGlobal); // "я глобальный" (в браузере)
6.2 Неявная глобализация
javascript
var a = 1; // глобальное свойство (configurable: false)
b = 2; // тоже глобальное (configurable: true)
delete a; // false (не удаляется)
delete b; // true (удаляется)
Часть 7. Асинхронность: Порядок, который обманывает
7.1 setTimeout с 0
javascript
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");
// Вывод: 1, 3, 2 (а не 1,2,3)
setTimeout с 0 не выполняется сразу. Он отправляет функцию в очередь макрозадач.
7.2 Микрозадачи vs Макрозадачи
javascript
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// Вывод: 1, 4, 3, 2
// Микрозадачи (promise.then) выполняются перед макрозадачами (setTimeout)
Часть 8. Классические загадки JavaScript
8.1 Странное сложение
javascript
[] + []; // ""
[] + {}; // "[object Object]"
{} + []; // 0
({} + []); // "[object Object]"
[] + []; // ""
[] + {}; // "[object Object]"
{} + {}; // NaN (в Firefox) или "[object Object][object Object]" (в Chrome)
8.2 Сравнения с массивами
javascript
[] == ![]; // true
[] == 0; // true
[1] == 1; // true
[1,2] == 1; // false
8.3 Минимальное число в JavaScript
javascript
Math.min() > Math.max(); // true (серьезно?)
Math.min(); // Infinity
Math.max(); // -Infinity
Часть 9. "Use Strict" - Режим без сюрпризов
Строгий режим исправляет многие "особенности":
javascript
"use strict";
// 1. Запрещены необъявленные переменные
accidentallyGlobal = "ошибка"; // ReferenceError
// 2. this в функциях не глобальный
function test() {
console.log(this); // undefined (не window)
}
// 3. Нельзя удалять неудаляемые свойства
delete Object.prototype; // TypeError
// 4. Дублирование параметров запрещено
function dup(a, a) {} // SyntaxError
// 5. eval не засоряет область видимости
eval("var x = 5");
console.log(x); // ReferenceError (x не виден)
Часть 10. Полезные особенности (которые вы могли не знать)
10.1 Оператор запятой
javascript
const result = (1, 2, 3, 4, 5); // 5 (возвращает последнее)
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j);
}
10.2 Деструктуризация с переименованием
javascript
const user = { name: "Анна", age: 25 };
const { name: userName, age: userAge } = user;
console.log(userName, userAge); // "Анна" 25
10.3 Тегированные шаблонные строки
javascript
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<strong>${values[i]}</strong>` : "");
}, "");
}
const name = "Анна";
const age = 25;
const html = highlight`Пользователь ${name} (возраст ${age})`;
// "Пользователь <strong>Анна</strong> (возраст <strong>25</strong>)"
10.4 Опциональная цепочка и нулевое слияние
javascript
const user = { profile: { name: "Анна" } };
// Старый способ
const name = user && user.profile && user.profile.name;
// Новый способ
const name = user?.profile?.name ?? "Гость";
Часть 11. Реальные последствия особенностей
11.1 Баг с сортировкой
javascript
[1, 2, 10, 20].sort();
// [1, 10, 2, 20] (сортировка как строки!)
// Правильно
[1, 2, 10, 20].sort((a, b) => a - b);
11.2 parseFloat и parseInt
javascript
parseInt("10px"); // 10
parseFloat("10.5px"); // 10.5
parseInt("10.5"); // 10
parseInt("08"); // 8 (старые браузеры могли дать 0)
parseInt("0x10"); // 16 (шестнадцатеричная)
// Всегда указывайте основание
parseInt("08", 10); // 8
11.3 Array.prototype.sort изменяет оригинал
javascript
const arr = [3, 1, 2];
const sorted = arr.sort(); // sort возвращает ссылку на тот же массив
console.log(arr === sorted); // true
console.log(arr); // [1, 2, 3] (оригинал изменен!)
Итог: Манифест принятия JavaScript
- typeof null === "object" - запомните и проверяйте через value === null.
- NaN !== NaN - используйте Number.isNaN().
- 0.1 + 0.2 !== 0.3 - работайте с погрешностью или целыми числами.
- [] + [] === "" - не пишите такой код.
- Строгий режим ("use strict") - всегда включайте.
- === вместо == - 99% случаев.
- let и const вместо var - забудьте про hoisting.
Финальный тест (проверьте себя):
javascript
// Что выведет?
console.log(typeof null);
console.log(0.1 + 0.2 === 0.3);
console.log([] + []);
console.log({} + []);
console.log(!![]);
console.log(NaN === NaN);
console.log([1, 2, 10].sort()[0]);
Ответы: "object", false, "", 0 (в некоторых средах), true, false, 1 (строковая сортировка дала [1, 10, 2]).
JavaScript не идеален. У него есть баги, странности и исторические артефакты. Но это язык, который работает везде, развивается без разрушения обратной совместимости и дает разработчикам невероятную свободу. Особенности JavaScript - это не проклятие. Это его ДНК. Примите их, изучите и используйте во благо. Или хотя бы не наступайте на одни и те же грабли дважды.