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

Язык JavaScript - Основы JavaScript 2

Циклы - это сердце любого алгоритма. Они делают скучную работу за нас: перебирают тысячи элементов, ждут нужного условия, строят сложные структуры. Но циклы - это еще и самый быстрый способ заморозить вкладку, если вы не знаете, что делаете. В JavaScript есть целое семейство циклов, но сегодня мы разберем двух китов: while и for. Они разные по характеру, но одинаково мощные. while говорит: "Пока условие истинно - делай. Когда станет ложным - остановись". javascript while (condition) {
// тело цикла
} Важнейший момент: условие проверяется перед каждой итерацией. Если изначально condition === false, тело не выполнится ни разу. javascript let i = 0;
while (i < 5) {
console.log(i);
i++;
}
// Выведет: 0, 1, 2, 3, 4 ✅ Когда количество итераций неизвестно заранее javascript // Ждем, пока пользователь введет корректное значение
let userInput = null;
while (userInput === null || userInput.trim() === "") {
userInput = prompt("Введите ваше имя:");
} ✅ Когда условие зависит от внешних факт
Оглавление
Взято с ya.ru
Взято с ya.ru

Бесконечность не предел: Полное руководство по циклам 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 - что выбрать?

-2

Часть 3. for - Цикл-контрол-фрик

for - самый популярный цикл в JS. Он собирает всё в одной строке: инициализацию, условие, пост-действие.

javascript

for (инициализация; условие; пост-действие) {
// тело цикла
}

Классический пример:

javascript

for (let i = 0; i < 5; i++) {
console.log(i);
}
// 0, 1, 2, 3, 4

Как это работает (пошагово):

  1. let i = 0 - выполняется один раз при входе
  2. i < 5 - проверка условия (если false - выход)
  3. Тело цикла console.log(i)
  4. i++ - пост-действие
  5. Переход к шагу 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;
}
}

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

  1. for - когда знаете количество итераций или перебираете массив.
  2. while - когда не знаете, сколько итераций, и условие может быть false с самого начала.
  3. do...while - когда тело должно выполниться хотя бы один раз.
  4. for...of - для перебора элементов (чище и безопаснее).
  5. for...in - только для объектов, и то с hasOwnProperty.
  6. Никогда не используйте var в циклах - только let.
  7. Бесконечные циклы - проверяйте инкремент/декремент.
  8. Производительность - на первом месте читаемость, оптимизируйте только при реальной необходимости.

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

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 - Битва эпох

-3

Когда использовать 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 и проваливанием
  • Когда нужна группировка нескольких значений
  • Когда код читается как "выбери один из вариантов"

Итог: Манифест переключателя

  1. switch использует строгое сравнение (===) - помните про типы.
  2. break обязателен - если только вы не используете fallthrough специально.
  3. default ставьте в конце - для всех необработанных случаев.
  4. Группируйте case для одинаковой логики - это красиво.
  5. Оборачивайте case в {} если объявляете переменные внутри.
  6. Для диапазонов и сложных условий - используйте if-else или switch(true) (с комментарием).
  7. Для простого маппинга - рассмотрите объект или 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)

Чистая функция:

  1. Одинаковый результат для одинаковых аргументов
  2. Не имеет побочных эффектов (не меняет внешние переменные)

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 = внешний)

Итог: Манифест функций

  1. Function Declaration - для глобальных функций, которые можно вызывать до объявления.
  2. Function Expression - когда нужна функция в переменной.
  3. Стрелочные функции - для коротких колбэков и сохранения this.
  4. Значения по умолчанию - всегда указывайте для необязательных параметров.
  5. Rest-параметры вместо arguments.
  6. Ранние возвраты (early return) - избавляют от вложенности.
  7. Замыкания - мощный инструмент для инкапсуляции.
  8. Чистые функции - легче тестировать и отлаживать.

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

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?

  1. Для рекурсии:

javascript

const factorial = function f(n) {
if (n <= 1) return 1;
return n * f(n - 1);
// f доступно внутри
};

// Внешнее имя factorial может быть перезаписано, но рекурсия продолжит работать

  1. Для отладки (стек трейс):

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 сегодня?

  1. Создание изолированной области видимости (до ES6):

javascript

// До появления let/const
(function() {
var privateVar = "Я не виден снаружи";
// код, который не засоряет глобальную область
})();

console.log(typeof privateVar);
// "undefined"

  1. Модульный паттерн (старый способ):

javascript

const myModule = (function() {
let privateData = "секрет";

function privateMethod() {
console.log(privateData);
}

return {
publicMethod: function() {
privateMethod();
}
};
})();

myModule.publicMethod();
// "секрет"

  1. Фиксация значений в циклах (старый способ):

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;

Ключевые отличия:

-4

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

  1. Function Expression не поднимается - вызывайте только после объявления.
  2. Анонимные функции - хороши для коротких колбэков, плохи для отладки.
  3. Named Function Expression - используйте для рекурсии и защиты от перезаписи.
  4. IIFE - классика, но сегодня редко нужен (есть let и модули).
  5. Function Expression в массивах/объектах - мощный паттерн для динамического кода.
  6. Всегда ставьте ; после Function Expression - защита от ошибок с IIFE.
  7. Для 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");

Выбирайте по читаемости, а не по скорости.

Итог: Манифест стрелочных функций

  1. Короткий синтаксис - пишите меньше, делайте больше.
  2. Нет своего this - берут из внешнего контекста (это суперсила для колбэков).
  3. Не использовать как методы объектов - для методов нужны обычные функции.
  4. Не использовать как конструкторы - для классов есть class.
  5. Нет arguments - используйте ...rest.
  6. Идеальны для колбэков - map, filter, reduce, setTimeout, промисы.
  7. Читаемость важнее краткости - не жертвуйте понятностью ради одной строки.

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

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

  1. typeof null === "object" - запомните и проверяйте через value === null.
  2. NaN !== NaN - используйте Number.isNaN().
  3. 0.1 + 0.2 !== 0.3 - работайте с погрешностью или целыми числами.
  4. [] + [] === "" - не пишите такой код.
  5. Строгий режим ("use strict") - всегда включайте.
  6. === вместо == - 99% случаев.
  7. 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 - это не проклятие. Это его ДНК. Примите их, изучите и используйте во благо. Или хотя бы не наступайте на одни и те же грабли дважды.