Этот курс охватывает JavaScript от базовых концепций до продвинутых тем. Мы добавили дополнительные объяснения, особенно в разделах среднего и сложного уровня, включая детальный разбор конструкций, методов, примеров использования, потенциальных ошибок и лучших практик. Для средних тем (например, функции, асинхронность) мы углубимся в механизмы работы, а для сложных (ООП, паттерны) — в внутреннюю логику, альтернативы и реальные сценарии применения. Каждый раздел включает больше примеров кода с комментариями.
По запросу добавлены новые разделы по TypeScript (Часть 11), поскольку TypeScript является надстройкой над JavaScript, добавляющей статическую типизацию. Это позволит перейти от динамического JS к типизированному коду, улучшая безопасность и масштабируемость проектов. Разделы по TypeScript построены аналогично структуре курса по JS, с акцентом на различия и преимущества.
Когда вы начинаете программировать, представьте, что у вас есть пустой рабочий стол и много разных вещей (данных), которые нужно куда-то складывать. Переменная — это как подписанная коробка, в которую вы кладёте данные. Вы даёте коробке имя (например, "возрастПользователя") и кладёте туда значение (например, число 25).
Зачем это нужно? Без переменных пришлось бы каждый раз писать одни и те же значения заново. Представьте, что в вашей программе есть важное число 3.14159 (число Пи). Вы используете его 20 раз в разных местах. А потом понимаете, что нужно более точное значение — 3.1415926535. Придётся менять во всех 20 местах! Но если вы положили это число в коробку с именем pi, то меняете значение только в одном месте — там, где объявляете переменную.
В JavaScript исторически было три способа создать такую коробку. Давайте разберём каждый на простой аналогии:
// 1. var — СТАРАЯ КОРОБКА С ПРОБЛЕМАМИ (почти не используется в современном коде)
// Представьте картонную коробку, которая может случайно открываться в разных комнатах.
var стараяПеременная = "значение";
// Проблема: если создать такую коробку в маленькой комнате (внутри {} скобок),
// её всё равно можно взять из соседней комнаты. Это приводит к ошибкам.
// ПРИМЕР ПРОБЛЕМЫ:
for (var i = 0; i < 3; i++) {
// Внутри этой "комнаты" создаётся коробка i
}
console.log(i); // ⚠️ ОШИБКА ЛОГИКИ, но код работает! i = 3
// Коробка i "утекла" за пределы своей комнаты. Так быть не должно!
// 2. let — НОРМАЛЬНАЯ КОРОБКА ДЛЯ ИЗМЕНЯЕМЫХ ВЕЩЕЙ
// Как пластиковый контейнер с крышкой. То, что внутри, можно менять.
let счётчик = 0;
счётчик = 1; // ✅ Можно положить новое значение
счётчик = счётчик + 5; // ✅ Можно взять текущее значение, что-то сделать с ним и положить обратно
счётчик += 3; // ✅ Короткая запись: счётчик = счётчик + 3
// ГДЕ ЖИВЁТ let-переменная (область видимости):
{
let видитТолькоЗдесь = "секрет";
console.log(видитТолькоЗдесь); // ✅ Работает
}
// console.log(видитТолькоЗдесь); // ❌ ОШИБКА! За пределами {} эта переменная не видна
// 3. const — ЗАПЕЧАТАННАЯ КОРОБКА (константа)
// Как банка с вареньем, которую нельзя заменить на другую банку.
// Но само варенье внутри можно есть (если это объект или массив).
const математическаяКонстанта = 3.14159;
// математическаяКонстанта = 3.14; // ❌ КАТАСТРОФА! Нельзя заменить банку!
// ВАЖНЫЙ НЮАНС: что значит "нельзя изменить" для разных типов данных?
const списокПокупок = ["хлеб", "молоко"];
// списокПокупок = ["чай", "кофе"]; // ❌ Нельзя заменить весь массив на новый
// НО МОЖНО делать это:
списокПокупок.push("яйца"); // ✅ Добавить в конец: ["хлеб", "молоко", "яйца"]
списокПокупок[0] = "батон"; // ✅ Заменить первый элемент: ["батон", "молоко", "яйца"]
списокПокупок.pop(); // ✅ Удалить последний элемент: ["батон", "молоко"]
const пользователь = { имя: "Иван", возраст: 25 };
пользователь.возраст = 26; // ✅ Изменить свойство
пользователь.город = "Москва"; // ✅ Добавить новое свойство
// пользователь = { имя: "Петр" }; // ❌ Нельзя заменить весь объект
// ПРАВИЛО ДЛЯ НАЧИНАЮЩИХ:
// 1. Всегда начинайте с const
// 2. Меняйте на let только если видите ошибку "Assignment to constant variable"
// 3. Никогда не используйте var (забудьте про него)
Теория — это хорошо, но программирование учатся на практике. Поэтому в нашем курсе есть:
Попробуйте прямо сейчас в редакторе: создайте const с массивом, добавьте в него элемент, а потом попробуйте заменить весь массив. Увидите ошибку, которая поможет запомнить разницу.
JavaScript различает, ЧТО именно вы кладёте в переменную. Это как на кухне: муку хранят в банке, ножи — в ящике, а кастрюли — в шкафу. Каждому типу данных — своё "место" и свои правила.
Это как отдельные предметы: одно яблоко, один нож, один лист бумаги. Их нельзя "разобрать на части" в контексте языка.
| Тип | Пример | Объяснение для новичка | Важные особенности | Как проверить |
|---|---|---|---|---|
| string (строка) |
"Привет"'Мир'`Шаблон ${x}` |
Любой текст. Всегда в кавычках! Одинарные ' ', двойные " " или обратные ` ` (для шаблонов). |
Нельзя изменить часть строки, только создать новую. "кот"[0] = "т" — не сработает! |
typeof "текст" → "string" |
| number (число) |
42-3.141e6 (1 миллион)Infinity |
Все числа: целые, дробные, отрицательные. Нет отдельного типа для целых чисел. | Есть проблема точности: 0.1 + 0.2 = 0.30000000000000004. Для денег используйте копейки (вместо 10.5 руб → 1050 коп). |
typeof 42 → "number" |
| boolean (логический) |
truefalse |
Только два значения: ДА (true) или НЕТ (false). Для принятия решений в программе. | Любое значение можно преобразовать в boolean. 0, "", null, undefined, NaN → false, всё остальное → true. |
typeof true → "boolean" |
| undefined | undefined |
Значение по умолчанию. Коробка создана, но в неё ещё ничего не положили. | let x; — x будет undefined. Обычно означает "значение отсутствует". |
typeof undefined → "undefined" |
| null | null |
Специальное значение "ничего", "пусто". Мы сами кладём null в переменную. | Историческая ошибка: typeof null → "object". Это баг языка, который исправить нельзя. |
Проверяйте так: x === null |
| bigint | 12345678901234567890n |
Огромные целые числа. Добавьте n в конце. Для расчётов, где обычные числа теряют точность. |
Нельзя смешивать с number без преобразования: 5n + 2 — ошибка! |
typeof 10n → "bigint" |
| symbol | Symbol("id")Symbol() |
Уникальный идентификатор. Как серийный номер, который никогда не повторяется. | Используется для скрытых свойств объектов. Пока просто знайте, что такой тип есть. | typeof Symbol() → "symbol" |
Это как ящик с инструментами или корзина с фруктами. Содержат внутри себя другие значения.
// 1. ОБЪЕКТ (object) — набор пар "ключ: значение"
const человек = {
имя: "Анна", // ключ "имя", значение "Анна" (строка)
возраст: 30, // ключ "возраст", значение 30 (число)
студент: true, // ключ "студент", значение true (boolean)
адрес: { // значение тоже может быть объектом!
город: "Москва",
улица: "Ленина"
}
};
// Как получить значение:
console.log(человек.имя); // "Анна" (через точку)
console.log(человек["возраст"]); // 30 (через квадратные скобки)
// 2. МАССИВ (array) — упорядоченный список
const оценки = [5, 4, 3, 5, 4]; // Нумерация с НУЛЯ!
console.log(оценки[0]); // 5 (первый элемент)
console.log(оценки[1]); // 4 (второй элемент)
// 3. ФУНКЦИЯ (function) — блок кода, который можно выполнять
function приветствие(имя) {
return `Привет, ${имя}!`;
}
console.log(typeof приветствие); // "function" (особый вид объекта)
Это самый сложный момент для новичков. Давайте разберём на аналогиях:
// СИТУАЦИЯ 1: const с ПРИМИТИВАМИ (string, number, boolean...)
const мойПароль = "секрет123";
// мойПароль = "новыйПароль"; // ❌ НЕЛЬЗЯ! Это как попытаться заменить надпись на запертой двери.
// СИТУАЦИЯ 2: const с МАССИВОМ
const холодильник = ["молоко", "яйца"];
// холодильник = ["колбаса", "сыр"]; // ❌ НЕЛЬЗЯ заменить весь холодильник!
// НО МОЖНО:
холодильник.push("масло"); // ✅ Добавить продукт
холодильник[0] = "кефир"; // ✅ Заменить молоко на кефир
холодильник.pop(); // ✅ Убрать последний продукт
// АНАЛОГИЯ: const с массивом — это закреплённая полка в холодильнике.
// Вы не можете заменить всю полку, но продукты на ней переставлять можно!
// СИТУАЦИЯ 3: const с ОБЪЕКТОМ
const настройки = { цвет: "синий", звук: true };
// настройки = { цвет: "красный" }; // ❌ НЕЛЬЗЯ заменить весь объект!
// НО МОЖНО:
настройки.цвет = "красный"; // ✅ Изменить свойство
настройки.яркость = 80; // ✅ Добавить новое свойство
delete настройки.звук; // ✅ Удалить свойство
// АНАЛОГИЯ: const с объектом — это закреплённая папка с документами.
// Вы не можете выбросить всю папку, но листы внутри менять можно!
Запоминалка: const защищает саму "коробку" (ссылку в памяти), а не то, что внутри (содержимое объектов/массивов).
Теперь, когда вы умеете складывать данные в переменные, давайте научимся их обрабатывать. Операции — это как инструменты для работы с данными.
let a = 10 + 5; // Сложение: 15
let b = 10 - 5; // Вычитание: 5
let c = 10 * 2; // Умножение: 20
let d = 10 / 2; // Деление: 5
let e = 11 % 3; // Остаток от деления: 2 (11 ÷ 3 = 3 и 2 в остатке)
let f = 2 ** 4; // Возведение в степень: 16 (2 × 2 × 2 × 2)
// КОРОТКИЕ ЗАПИСИ (часто используются):
let число = 10;
число += 5; // то же, что число = число + 5
число -= 3; // то же, что число = число - 3
число *= 2; // то же, что число = число × 2
число /= 4; // то же, что число = число ÷ 4
число %= 3; // то же, что число = число % 3
// ИНКРЕМЕНТ и ДЕКРЕМЕНТ (увеличить/уменьшить на 1):
let счетчик = 0;
счетчик++; // Увеличить на 1 (постфиксная форма)
++счетчик; // Увеличить на 1 (префиксная форма)
счетчик--; // Уменьшить на 1
// В чём разница между ++x и x++?
let x = 5;
let y = x++; // 1) y получает СТАРОЕ значение x (5), 2) x увеличивается до 6
console.log(x, y); // 6, 5
let m = 5;
let n = ++m; // 1) m увеличивается до 6, 2) n получает НОВОЕ значение m (6)
console.log(m, n); // 6, 6
// КОНКАТЕНАЦИЯ (сложение строк):
let имя = "Иван";
let фамилия = "Петров";
let полноеИмя = имя + " " + фамилия; // "Иван Петров"
// ШАБЛОННЫЕ СТРОКИ (современный способ):
let возраст = 25;
let сообщение = `Меня зовут ${имя} и мне ${возраст} лет`;
// Результат: "Меня зовут Иван и мне 25 лет"
// Полезные методы строк:
let текст = " JavaScript это интересно ";
console.log(текст.length); // 30 (длина строки включая пробелы)
console.log(текст.toUpperCase()); // " JAVASCRIPT ЭТО ИНТЕРЕСНО "
console.log(текст.toLowerCase()); // " javascript это интересно "
console.log(текст.trim()); // "JavaScript это интересно" (убирает пробелы по краям)
console.log(текст.indexOf("интересно")); // 15 (позиция начала слова)
console.log(текст.includes("JS")); // false (содержит ли строка "JS"?)
console.log(текст.slice(2, 12)); // "JavaScript" (вырезать часть строки)
САМАЯ ВАЖНАЯ ЧАСТЬ ДЛЯ НАЧИНАЮЩИХ! Здесь много подводных камней.
// СТРОГОЕ СРАВНЕНИЕ (ИСПОЛЬЗУЙТЕ ВСЕГДА ЭТО!):
console.log(5 === 5); // true (число 5 равно числу 5)
console.log(5 === "5"); // false (число 5 НЕ равно строке "5")
console.log(5 !== 3); // true (5 не равно 3)
console.log(5 !== "5"); // true (число 5 не равно строке "5")
// НЕСТРОГОЕ СРАВНЕНИЕ (ИЗБЕГАЙТЕ! может давать странные результаты):
console.log(5 == "5"); // true (JavaScript пытается "угадать", преобразует типы)
console.log(0 == false); // true (0 и false считаются равными!)
console.log("" == false); // true (пустая строка и false считаются равными!)
console.log(null == undefined); // true (ещё одна странность)
// БОЛЬШЕ/МЕНЬШЕ:
console.log(10 > 5); // true
console.log(10 >= 10); // true (больше или равно)
console.log(3 < 5); // true
console.log(3 <= 3); // true (меньше или равно)
// СРАВНЕНИЕ СТРОК (по алфавиту):
console.log("яблоко" > "банан"); // true (буква "я" дальше в алфавите)
console.log("Анна" < "анна"); // true (заглавные буквы идут перед строчными)
console.log("10" > "2"); // false! (это строки, сравнивается посимвольно: "1" < "2")
// && (И) — true, если ОБА выражения true
console.log(true && true); // true
console.log(true && false); // false
console.log(10 > 5 && 3 < 4); // true (оба сравнения true)
// || (ИЛИ) — true, если ХОТЯ БЫ ОДНО выражение true
console.log(true || false); // true
console.log(false || false); // false
console.log(10 < 5 || 3 < 4); // true (второе выражение true)
// ! (НЕ) — инвертирует значение
console.log(!true); // false
console.log(!false); // true
console.log(!(5 > 3)); // false (5 > 3 это true, инвертируем → false)
// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ:
let возраст = 25;
let естьПаспорт = true;
let можетГолосовать = возраст >= 18 && естьПаспорт; // true
let можетВодить = возраст >= 18 || естьВодительскиеПрава; // зависит от прав
// КОРОТКОЕ ЗАМЫКАНИЕ (важная особенность):
let x = 0;
let y = 5;
// Если первая часть && false, вторая не вычисляется
console.log(x > 1 && y++); // false, y остаётся 5 (y++ не выполнился)
// Если первая часть || true, вторая не вычисляется
console.log(x === 0 || y++); // true, y остаётся 5
// Обычный if-else:
let доступ;
if (возраст >= 18) {
доступ = "взрослый";
} else {
доступ = "ребёнок";
}
// Тернарный оператор (то же самое в одну строку):
let доступ = (возраст >= 18) ? "взрослый" : "ребёнок";
// Синтаксис: условие ? значениеЕслиTrue : значениеЕслиFalse
// Более сложный пример:
let цена = 100;
let итоговаяЦена = (цена > 50) ? цена * 0.9 : цена; // 10% скидка, если цена > 50
// Если цена > 50, то итоговаяЦена = цена * 0.9, иначе итоговаяЦена = цена
// NaN (Not a Number) — результат некорректной математической операции
console.log(0 / 0); // NaN
console.log("текст" * 2); // NaN
console.log(Math.sqrt(-1)); // NaN
// Важно: NaN не равно даже самому себе!
console.log(NaN === NaN); // false (!)
// Проверяйте так:
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true (более надёжно)
// Бесконечность:
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(Infinity > 1000000); // true
// Приведение типов (когда JavaScript сам меняет типы):
console.log("5" + 3); // "53" (число 3 превращается в строку "3")
console.log("5" - 3); // 2 (строка "5" превращается в число 5)
console.log("5" * "2"); // 10 (обе строки превращаются в числа)
console.log(true + 1); // 2 (true превращается в 1)
console.log(false + 1); // 1 (false превращается в 0)
Что выведет этот код? Проверьте свои догадки в нашем встроенном редакторе.
let a = "10";
let b = 5;
console.log(a + b); // 1. ?
console.log(a - b); // 2. ?
console.log(a > b); // 3. ?
let x = 0;
let y = "0";
console.log(x == y); // 4. ?
console.log(x === y); // 5. ?
let list = [1, 2, 3];
list.push(4);
list[0] = 10;
console.log(list); // 6. ?
const obj = { prop: "значение" };
obj.prop = "новое";
// obj = { другой: "объект" }; // 7. Что будет, если раскомментировать?
Ответы: 1) "105", 2) 5, 3) true, 4) true, 5) false, 6) [10, 2, 3, 4], 7) Ошибка "Assignment to constant variable"
Главный совет: Не пытайтесь запомнить всё сразу! Программирование — это навык, который развивается практикой. Используйте тренажёр задач после каждого раздела, экспериментируйте в редакторе, делайте ошибки и исправляйте их. Через несколько практических занятий эти концепции станут естественными.
Условия позволяют коду принимать решения на основе данных. if-else — базовая конструкция, switch — для множественных случаев.
// if-else: проверяет условие (truthy/falsy). Можно цепочкой.
let weather = "дождь";
if (weather === "дождь") {
console.log("Возьми зонт!");
} else if (weather === "солнце") {
console.log("Надень очки!");
} else {
console.log("Одевайся обычно");
}
// Пример с несколькими условиями:
if (age >= 18 && hasID) {
console.log("Вход разрешен");
} else {
console.log("Вход запрещен");
}
// switch-case: для сравнения с несколькими значениями. Использует строгое ===. Без break — fall-through (провалится в следующий case).
let day = "понедельник";
switch(day) {
case "понедельник":
console.log("Начало недели");
break; // Обязателен, иначе выполнит все ниже!
case "вторник":
case "среда":
case "четверг":
console.log("Будни"); // Группировка случаев без break между ними
break;
case "пятница":
console.log("Пятница!");
break;
default:
console.log("Выходной"); // Для всех остальных
}
Расширенное объяснение: switch эффективен для enum-подобных значений (строки, числа). Альтернатива — объект с ключами: const actions = { "дождь": () => "Зонт", "солнце": () => "Очки" }; actions[weather]() || "Обычно";. Это более гибко для динамических случаев. Избегайте глубоких вложенных if — используйте early return для читаемости.
Циклы повторяют код. Выберите тип в зависимости от сценария: известное количество итераций (for), условие (while), итерация по коллекциям (for...of/in).
// for: классический, для известного количества. Синтаксис: инициализация; условие; инкремент.
for(let i = 0; i < 5; i++) {
console.log("Шаг", i); // i локальна благодаря let
}
// Вывод: Шаг 0 ... Шаг 4
// Расширенный: вложенные циклы для матриц
for(let row = 0; row < 3; row++) {
for(let col = 0; col < 3; col++) {
console.log(`Клетка ${row},${col}`);
}
}
// while: пока условие true. Полезен, когда итерации зависят от внешних факторов.
let attempts = 0;
while(attempts < 3) {
console.log("Попытка номер", attempts + 1);
attempts++;
}
// do-while: гарантирует хотя бы одно выполнение (постпроверка).
let userInput;
do {
userInput = prompt("Введите пароль:"); // Спросит минимум раз
} while(userInput !== "12345");
// for...of: итерация по iterable (массивы, строки, Set, Map). Получает значение.
let fruits = ["яблоко", "банан", "апельсин"];
for(let fruit of fruits) {
console.log(fruit);
}
// for...in: итерация по перечисляемым свойствам объектов (ключи). Не для массивов (порядок не гарантирован).
let person = {name: "Анна", age: 25};
for(let key in person) {
console.log(key, ":", person[key]);
}
Расширенное: Для прерывания используйте break (выход из цикла), continue (пропуск итерации). Избегайте бесконечных циклов (добавьте счетчики). Для производительности кэшируйте длину: for(let i=0, len=array.length; i<len; i++). В ES6+ prefer for...of для читаемости.
Массивы — упорядоченные коллекции. Они динамические (размер меняется), индексированы с 0. Массивы — это объекты, но с особым поведением.
// Создание: литерал [] или new Array().
let emptyArray = [];
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "текст", true, null]; // Гетерогенные типы разрешены
// Доступ: по индексу. Отрицательные индексы не поддерживаются (используйте at() в ES2022).
let first = numbers[0]; // 1
let last = numbers[numbers.length - 1]; // 5
console.log(numbers.at(-1)); // 5 (ES2022, отрицательный индекс от конца)
// Мутация: массивы мутабельны.
numbers[0] = 10; // [10, 2, 3, 4, 5]
numbers.push(6); // [10, 2, 3, 4, 5, 6] - добавляет в конец, возвращает новую длину
let popped = numbers.pop(); // 6, массив: [10, 2, 3, 4, 5]
numbers.unshift(0); // [0, 10, 2, 3, 4, 5] - добавляет в начало (медленно для больших массивов)
numbers.shift(); // [10, 2, 3, 4, 5] - удаляет первый
Методы массивов (средний уровень): Многие методы возвращают новый массив (иммутабельные), другие мутируют оригинал. Это функциональный стиль (map, filter) vs императивный.
let fruits = ["Яблоко", "Банан", "Апельсин"];
// map: трансформирует каждый элемент, возвращает новый массив.
let upperFruits = fruits.map(fruit => fruit.toUpperCase()); // ["ЯБЛОКО", "БАНАН", "АПЕЛЬСИН"]
// Внутри: callback(элемент, индекс, массив)
// filter: возвращает новый массив с элементами, прошедшими тест.
let longFruits = fruits.filter(fruit => fruit.length > 5); // ["Яблоко", "Апельсин"]
// find: возвращает первый элемент, прошедший тест (или undefined).
let found = fruits.find(fruit => fruit.startsWith("Б")); // "Банан" (startsWith - метод строки)
// forEach: выполняет callback для каждого, ничего не возвращает (для side-effects).
fruits.forEach((fruit, index) => console.log(`${index}: ${fruit}`));
// reduce: аккумулирует значение (сумма, max и т.д.). Callback(аккумулятор, элемент, индекс, массив), initialValue.
let numbers2 = [1, 2, 3, 4];
let sum = numbers2.reduce((total, num) => total + num, 0); // 10
let max = numbers2.reduce((max, num) => Math.max(max, num), -Infinity); // 4
// slice: копирует часть, не мутирует. slice(start, end) - end не включительно.
let part = fruits.slice(1, 3); // ["Банан", "Апельсин"]
let copy = fruits.slice(); // Полная копия
// splice: мутирует, удаляет/вставляет. splice(start, deleteCount, ...items)
fruits.splice(1, 1, "Груша"); // Удаляет 1 элемент с индекса 1, вставляет "Груша". fruits: ["Яблоко", "Груша", "Апельсин"]
Расширенное: some/every — проверка условий (возвращают boolean). indexOf/lastIndexOf — поиск позиции. flat/flatMap — для вложенных массивов. Ошибки: Не путайте мутирующие методы (push, splice) с иммутабельными (map, filter) — это важно в React/Redux. Для глубоких копий используйте JSON.parse(JSON.stringify(arr)) или structuredClone (ES2022).
Объекты — неупорядоченные коллекции ключ-значение (хэш-мапы). Ключи — строки или Symbol. Объекты мутабельны, передаются по ссылке.
// Создание: литерал {} или new Object().
let user = {
firstName: "Иван", // Свойство (key: value)
lastName: "Петров",
age: 30;
isAdmin: false,
// Метод: функция как значение.
getFullName: function() { // Классическая запись
return this.firstName + " " + this.lastName; // this - ссылка на объект
},
greet() { // Короткая запись (ES6), эквивалентно function greet()
return `Привет, я ${this.firstName}!`;
}
};
// Доступ: точка (для известных ключей) или скобки (для динамических).
console.log(user.firstName); // "Иван"
console.log(user["lastName"]); // "Петров" (полезно для переменных: let key = "age"; user[key])
// Мутация:
user.email = "ivan@example.com"; // Добавление
user["phone"] = "+79123456789";
user.age = 31; // Изменение
// Удаление:
delete user.isAdmin; // Удаляет свойство
// Проверка:
if ("age" in user) { // true
console.log("Возраст указан");
}
console.log(user.hasOwnProperty("age")); // true (только собственные свойства, не прототипные)
Расширенное: Object.keys(user) — массив ключей, Object.values — значений, Object.entries — пар [key, value]. Object.assign(target, ...sources) — слияние объектов (поверхностное). Для глубокого слияния используйте библиотеки как lodash. this в методах: Контекст зависит от вызова (user.greet() — this=user). Потеря this в callbacks — решайте arrow functions или bind.
// Пример потери this:
let greetFunc = user.greet;
greetFunc(); // undefined (this=window или undefined в strict mode)
// Решение:
let boundGreet = user.greet.bind(user); // Фиксирует this
boundGreet(); // Работает
Лучшая практика: Используйте короткую запись методов. Для immutable объектов — Object.freeze(user) (запрещает изменения).
Есть три способа объявления: declaration (hoisting), expression, arrow (ES6, нет своего this/arguments).
// 1. Declaration: поднимается, можно вызвать до объявления.
function sayHello(name) { // Параметры в ()
return `Привет, ${name}!`; // return - выход с значением
}
sayHello("Мир"); // "Привет, Мир!" (аргументы)
// 2. Expression: присваивается переменной, нет hoisting.
const sayGoodbye = function(name) {
return `Пока, ${name}!`;
};
// 3. Arrow: компактная, нет своего this (берет из окружения), не может быть конструктором.
const add = (a, b) => a + b; // Неявный return для одной строки
const square = x => x * x; // Для одного параметра без ()
const greet = name => { // Блок {} требует явного return
const message = `Привет, ${name}`;
return message;
};
Параметры: По умолчанию, rest, деструктуризация (средний уровень).
// Default parameters (ES6):
function greet(name = "Гость") { // Если аргумент не передан
return `Привет, ${name}`;
}
greet(); // "Привет, Гость"
// Rest: собирает аргументы в массив.
function sumAll(...numbers) { // ... - rest operator
return numbers.reduce((total, num) => total + num, 0);
}
sumAll(1, 2, 3, 4); // 10
// Деструктуризация: извлекает свойства из объектов/массивов.
function printUser({name, age = 18}) { // Default в деструктуре
console.log(`${name}, ${age} лет`);
}
printUser({name: "Анна", age: 25}); // "Анна, 25 лет"
Arguments объект: Псевдомассив всех аргументов (устарел, prefer rest).
Ошибки: Передача лишних аргументов игнорируется, недостающие — undefined. Используйте strict mode ("use strict") для строгих правил.
// 1. Global: вне функций, видна везде (избегайте загрязнения).
let globalVar = "Я глобальная";
console.log(window.globalVar); // В браузере global = window
function test() {
console.log(globalVar); // Доступна
}
// 2. Function: внутри функций.
function outer() {
let outerVar = "Я в outer";
function inner() {
let innerVar = "Я в inner";
console.log(outerVar); // Лексическая видимость (внешний scope)
console.log(globalVar);
}
inner();
// console.log(innerVar); // ReferenceError (не видна)
}
// 3. Block: для let/const внутри {} (if, for, {}).
{
let blockVar = "Я в блоке";
var oldVar = "Я var"; // var - функциональный scope, игнорирует блоки
}
// console.log(blockVar); // ReferenceError
console.log(oldVar); // "Я var"
Scope chain: Поиск переменной идет вверх по цепочке scopes до global. Strict mode: Запрещает неявные глобальные переменные.
function createCounter() {
let count = 0; // Приватная, сохраняется в замыкании
return function() { // Внутренняя функция замыкает count
count++; // Доступ к внешнему scope
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter(); // Независимое замыкание
console.log(counter2()); // 1
Практика (средний уровень): Модульный паттерн для приватности.
function createBankAccount(initialBalance) {
let balance = initialBalance; // Приватная переменная
return { // Объект с методами, замыкающими balance
deposit(amount) {
if (amount > 0) { // Валидация
balance += amount;
}
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return "Недостаточно средств";
},
getBalance() {
return balance; // Только чтение
}
};
}
let account = createBankAccount(1000);
account.deposit(500); // 1500
// balance недоступна напрямую — инкапсуляция
Расширенное: Замыкания потребляют память (держат scope живым). В циклах с var — проблема (все замыкания видят последнее значение), решайте let или IIFE. Применение: currying, event handlers.
// Currying с замыканием
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
double(5); // 10
Методы document для поиска. querySelector — мощный, как CSS.
// getElementById: быстрый, по уникальному id.
let header = document.getElementById("header"); // null если не найден
// getElementsByClassName: коллекция (HTMLCollection, живая — обновляется автоматически).
let items = document.getElementsByClassName("item"); // Массивоподобный, for...of работает
// getElementsByTagName: по тегу.
let paragraphs = document.getElementsByTagName("p");
// querySelector: первый по CSS-селектору.
let element = document.querySelector(".container .item"); // .class, #id, tag, [attr]
// querySelectorAll: все, возвращает NodeList (статический, forEach поддерживает).
let allElements = document.querySelectorAll(".item");
Расширенное: Поиск от элемента: element.querySelector(".child"). Живые vs статические коллекции: getElements* — живые (изменения DOM отражаются), querySelectorAll — снимок.
Манипуляция текстом, стилями, классами.
let div = document.getElementById("myDiv");
// Текст: textContent - чистый текст (безопасный от XSS), innerHTML - с HTML (опасный, если пользовательский ввод).
div.textContent = "Новый текст";
div.innerHTML = "<strong>Жирный</strong> текст"; // Парсит HTML
// Стили: inline styles.
div.style.color = "red"; // camelCase для свойств вроде background-color
div.style.backgroundColor = "#f0f0f0";
div.style.display = "none"; // Скрыть (удаляет из потока)
div.style.visibility = "hidden"; // Скрыть, но занимает место
// Классы: classList - объект для манипуляции.
div.classList.add("active", "visible"); // Добавляет несколько
div.classList.remove("old");
div.classList.toggle("hidden"); // Добавляет/удаляет
div.classList.contains("active"); // true/false
div.classList.replace("old", "new"); // Замена
Расширенное: Для производительности prefer classList над style (CSS правила лучше). Атрибуты: div.setAttribute("data-id", "123"), div.getAttribute("data-id").
События — реакции на действия пользователя/DOM. addEventListener — стандарт.
let button = document.querySelector("button");
// onclick: старый, перезаписывает (не используйте).
button.onclick = function() {
console.log("Клик!");
};
// addEventListener: добавляет несколько, options (once, passive).
button.addEventListener("click", function(event) { // event - объект с деталями
console.log("Клик!", event.target, event.type); // target - элемент, type - "click"
});
// Основные события:
element.addEventListener("click", handler); // Клик (мышь)
element.addEventListener("dblclick", handler); // Двойной клик
element.addEventListener("mouseover", handler); // Наведение
element.addEventListener("mouseout", handler); // Уход
element.addEventListener("focus", handler); // Фокус (для input)
element.addEventListener("blur", handler); // Потеря фокуса
element.addEventListener("keydown", handler); // Клавиша вниз (event.key, event.code)
element.addEventListener("submit", handler); // Форма (для form)
// Удаление: removeEventListener (нужен тот же handler).
let handler = () => console.log("Click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
// Пропагация: bubbling (всплытие от target вверх), capturing (захват сверху вниз).
// Отмена: event.preventDefault() - отменяет default (submit, click на link)
link.addEventListener("click", function(event) {
event.preventDefault(); // Не перейти по ссылке
event.stopPropagation(); // Остановить всплытие
event.stopImmediatePropagation(); // Остановить все handlers на этом элементе
});
Расширенное (средний уровень): Delegation — слушатель на родителе, проверка event.target.matches(".selector"). Полезно для динамических элементов. Custom events: new CustomEvent("myEvent", {detail: data}), element.dispatchEvent.
Динамическое создание/удаление.
// createElement: создает узел.
let newDiv = document.createElement("div"); // <div></div>
newDiv.textContent = "Я новый элемент!";
// Добавление:
document.body.appendChild(newDiv); // В конец body
parentElement.insertBefore(newDiv, referenceElement); // Перед reference
parentElement.append(newDiv, anotherDiv); // ES2016, несколько
parentElement.prepend(newDiv); // В начало
// Удаление:
let element = document.getElementById("toRemove");
element.remove(); // Простой
// Старый: element.parentNode.removeChild(element);
// Клонирование:
let clone = element.cloneNode(true); // true - глубокое (с детьми), false - поверхностное
Расширенное: Для массовых изменений используйте DocumentFragment (виртуальный контейнер, минимизирует reflows).
let frag = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
let p = document.createElement("p");
p.textContent = `Параграф ${i}`;
frag.appendChild(p);
}
document.body.appendChild(frag); // Одна вставка
Callbacks — функции, передаваемые как аргументы, вызываются по завершении.
// Пример: setTimeout - асинхронный таймер.
function getData(callback) {
setTimeout(() => { // Имитация задержки (network, IO)
console.log("Данные получены");
callback("data");
}, 1000);
}
getData(function(data) {
console.log("Обработали:", data);
getMoreData(function(moreData) { // Callback hell - вложенность, сложно читать/ошибки
console.log("Еще:", moreData);
});
});
Проблемы: Pyramid of doom, сложная обработка ошибок (нужен callback для error). Альтернативы: Promises.
Promise — объект для асинхронных операций. Состояния: pending, fulfilled (resolve), rejected (reject).
// Создание:
let promise = new Promise((resolve, reject) => { // Executor выполняется сразу
setTimeout(() => {
let success = true;
if (success) {
resolve("Данные загружены!"); // fulfilled
} else {
reject(new Error("Ошибка загрузки")); // rejected
}
}, 2000);
});
// Цепочка: then (для resolve), catch (для reject), finally (всегда).
promise
.then(result => { // result из resolve
console.log("Успех:", result);
return result + " обработаны"; // Передает в следующий then
})
.then(processed => {
console.log("Обработано:", processed);
throw new Error("Имитация ошибки"); // Перейдет в catch
})
.catch(error => { // Ловит ошибки из then/reject
console.error("Ошибка:", error.message);
})
.finally(() => {
console.log("Завершено"); // Очистка
});
Расширенное (средний уровень): Promise.all([p1, p2]) — ждет все, reject при первой ошибке. Promise.race — первый завершенный. Promise.allSettled — все, с статусами. Promise.any — первый fulfilled. Ошибки: Всегда добавляйте catch, иначе unhandled rejection.
Синтаксический сахар над Promises. Делает код линейным.
async function loadData() { // async - возвращает Promise
try { // Для ошибок
console.log("Начинаем...");
let data = await fetchData(); // await - ждет resolve, бросает reject в catch
console.log("Данные:", data);
let processed = await processData(data);
console.log("Обработано:", processed);
return processed; // resolve
} catch (error) {
console.error("Ошибка:", error);
throw error; // reject
}
}
// Использование: как Promise
loadData()
.then(result => console.log("Финал:", result))
.catch(err => console.log("Ошибка в вызове:", err));
Расширенное: В циклах: for await (of asyncIterable). Параллель: let [d1, d2] = await Promise.all([p1, p2]). Top-level await в модулях. Ошибки: Всегда try-catch, await только в async.
Built-in для запросов. Возвращает Promise с Response.
// GET: простой запрос.
async function fetchUsers() {
try {
let response = await fetch('https://api.example.com/users'); // GET по умолчанию
if (!response.ok) { // Проверка статус 200-299
throw new Error(`HTTP ошибка: ${response.status}`); // 404, 500 и т.д.
}
let users = await response.json(); // Парсит body как JSON (Promise)
// Альтернативы: .text(), .blob(), .formData()
console.log(users);
return users;
} catch (error) {
console.error("Ошибка:", error);
return []; // Fallback
}
}
// POST: с опциями.
async function createUser(userData) {
let response = await fetch('https://api.example.com/users', {
method: 'POST', // GET, POST, PUT, DELETE
headers: { // Заголовки
'Content-Type': 'application/json',
'Authorization': 'Bearer token' // Для аутентификации
},
body: JSON.stringify(userData), // Stringify для JSON
credentials: 'include', // Cookies
mode: 'cors' // CORS
});
if (!response.ok) {
throw new Error(await response.text());
}
return await response.json();
}
Расширенное (средний уровень): AbortController для отмены: let controller = new AbortController(); fetch(url, {signal: controller.signal}); controller.abort(). Headers — Map-подобный. Для form: new FormData(formElement).
ООП в JS прототипное, не классовое (под капотом прототипы). ES6 классы — сахар.
Функции для создания объектов с общими методами.
function Person(name, age) { // Конструктор (new вызывает)
this.name = name;
this.age = age;
this.greet = function() { // Метод на экземпляре (копируется каждый раз - неэффективно)
console.log(`Привет, я ${this.name}`);
};
}
let person1 = new Person("Анна", 25); // new создает {}, устанавливает this, прототип
person1.greet();
// Проблема: методы дублируются. Решение: прототип.
Расширенное: Без new — this=global, ошибки. Проверяйте: if (!(this instanceof Person)) return new Person(...).
Прототип — объект для общих свойств. Цепочка прототипов (inheritance).
function Person(name, age) {
this.name = name;
this.age = age;
}
// Прототип: общие методы
Person.prototype.greet = function() {
console.log(`Привет, я ${this.name}`);
};
Person.prototype.getBirthYear = function() {
return new Date().getFullYear() - this.age;
};
let p = new Person("Иван", 30);
p.greet(); // Находит в прототипе
// Наследование:
function Student(name, age, major) {
Person.call(this, name, age); // Вызов супер-конструктора
this.major = major;
}
Student.prototype = Object.create(Person.prototype); // Связь прототипов
Student.prototype.constructor = Student; // Восстановление
Student.prototype.study = function() {
console.log(`${this.name} изучает ${this.major}`);
};
let s = new Student("Мария", 20, "Математика");
s.greet(); // Из Person.prototype
s.study();
console.log(s instanceof Student); // true
console.log(s instanceof Person); // true (цепочка)
Расширенное (сложный уровень): __proto__ - ссылка на прототип (не используйте напрямую). Object.getPrototypeOf(obj). Для mixin: Object.assign(Student.prototype, mixinObj). Прототипы экономят память.
Сахар над прототипами. Более читаемо. Мы добавили дополнительные примеры, включая использование приватных полей (#), статических свойств, геттеров/сеттеров в реальных сценариях, а также примеры полиморфизма и композиции.
// Базовый класс
class Person {
// Конструктор
constructor(name, age) {
this.name = name;
this.age = age;
}
// Метод
greet() {
console.log(`Привет, я ${this.name}`);
}
// Геттер (вызывается как свойство)
get birthYear() {
return new Date().getFullYear() - this.age;
}
// Сеттер
set nickname(value) {
this._nickname = value;
}
// Статический метод (принадлежит классу, а не объекту)
static compareAge(a, b) {
return a.age - b.age;
}
}
// Наследование
class Student extends Person {
constructor(name, age, major) {
super(name, age); // Вызов родительского конструктора
this.major = major;
}
// Переопределение метода (полиморфизм)
greet() {
super.greet(); // Вызов родительского метода
console.log(`Я изучаю ${this.major}`);
}
// Свой метод
study() {
console.log(`${this.name} готовится к экзамену`);
}
}
// Использование базового класса
let person = new Person("Алексей", 35);
person.greet(); // "Привет, я Алексей"
console.log(person.birthYear); // Текущий год - 35
person.nickname = "Alex"; // Установка через setter
console.log(person._nickname); // "Alex"
// Использование наследуемого класса
let student = new Student("Иван", 20, "Информатика");
student.greet(); // "Привет, я Иван" + "Я изучаю Информатика"
student.study(); // "Иван готовится к экзамену"
console.log(Person.compareAge(person, student)); // 15 (35 - 20)
// Дополнительный пример: Класс с приватными полями (ES2022+)
class BankAccount {
#balance; // Приватное поле (недоступно извне)
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
return this.#balance;
}
get balance() { // Геттер для чтения приватного
return this.#balance;
}
static createSavings(initial) { // Статический фабричный метод
return new BankAccount(initial);
}
}
let account = BankAccount.createSavings(1000); // Использование статического метода
account.deposit(500);
console.log(account.balance); // 1500 (через геттер)
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
// Дополнительный пример: Полиморфизм с несколькими классами
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log("Звук животного");
}
}
class Dog extends Animal {
makeSound() {
console.log("Гав!");
}
}
class Cat extends Animal {
makeSound() {
console.log("Мяу!");
}
}
// Массив с полиморфизмом
const animals = [new Dog("Бобик"), new Cat("Мурка")];
animals.forEach(animal => animal.makeSound()); // "Гав!" "Мяу!" (вызов переопределенных методов)
// Дополнительный пример: Композиция (вместо глубокого наследования)
class Engine {
start() {
console.log("Двигатель запущен");
}
}
class Car {
constructor() {
this.engine = new Engine(); // Композиция: имеет Engine
}
drive() {
this.engine.start();
console.log("Машина едет");
}
}
let myCar = new Car();
myCar.drive(); // "Двигатель запущен" "Машина едет"
Расширенное (сложный уровень): Приватные поля (#private, ES2022) обеспечивают настоящую инкапсуляцию. Super в методах для вызова родительских. Классы не поднимаются (no hoisting). Для полиморфизма переопределяйте методы. Предпочитайте композицию над наследованием для гибкости (принцип "has-a" vs "is-a"). Ошибки: Забыть super() в наследнике — ReferenceError. Используйте instanceof для проверки типа.
Извлечение значений из объектов/массивов. ES6.
// Массивы: по позиции.
let numbers = [1, 2, 3, 4, 5];
let [first, second, , fourth, ...rest] = numbers; // Пропуск ,, rest - остаток
// first=1, second=2, fourth=4, rest=[5]
// Обмен:
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
// Объекты: по ключам.
let user = { name: "Анна", age: 25, city: "Москва" };
let { name, age, city = "Неизвестно" } = user; // Default
let { name: userName } = user; // Переименование
// Вложенная:
let { address: { street } } = { address: { street: "Main" } }; // street="Main"
Расширенное: В параметрах функций, с defaults: function({name="Гость"}={}) {}. Для модулей: import {a as b} from...
... для разворачивания/сбора.
// Spread: в массивах/объектах/вызовах.
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2, 7]; // [1,2,3,4,5,6,7]
let obj1 = { x: 1, y: 2 };
let obj2 = { y: 3, z: 4 };
let merged = { ...obj1, ...obj2 }; // {x:1, y:3, z:4} (перезаписывает)
Math.max(...arr1); // 3 (spread в аргументы)
// Rest: в параметрах/деструктуре.
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
Расширенное: Копирование: let copy = {...obj} (поверхностное). В React: для props.
ES6 модули для организации кода. Экспорт/импорт.
math.js:
export const PI = 3.14159; // Named export
export function sum(a, b) {
return a + b;
}
export default class Calculator { // Default export (один на модуль)
multiply(a, b) {
return a * b;
}
}
app.js:
import Calculator, { PI, sum as add } from './math.js'; // Default без {}, named в {}, as - alias
console.log(PI);
console.log(add(2, 3));
let calc = new Calculator();
console.log(calc.multiply(4, 5));
Расширенное (сложный уровень): Dynamic import: import("./mod.js").then(mod => ...). Tree shaking в bundlers (Webpack). CommonJS (require/exports) для Node.
Функции, возвращающие итераторы. yield — пауза/возврат.
function* numberGenerator() { // * - generator
yield 1; // Пауза, возвращает {value:1, done:false}
yield 2;
yield 3;
return 4; // {value:4, done:true}
}
let gen = numberGenerator();
console.log(gen.next()); // {value:1, done:false}
console.log(gen.next().value); // 2
// for...of: автоматически next()
for (let num of numberGenerator()) {
console.log(num); // 1 2 3 (return игнорируется)
}
// Двусторонняя: next(arg) - передает в yield
function* twoWay() {
let input = yield "Give me input";
console.log(input);
}
let g = twoWay();
g.next(); // {value:"Give me input", done:false}
g.next("Input"); // Выводит "Input"
Практика (сложный уровень): Ленивые последовательности.
function* fibonacci(limit) {
let [prev, curr] = [0, 1];
while (limit-- > 0) {
yield curr; // Вычисляет по требованию
[prev, curr] = [curr, prev + curr];
}
}
for (let num of fibonacci(10)) {
console.log(num); // 1 1 2 3 5 8 13 21 34 55
}
Расширенное: Async generators: async function*, yield await. Для streams, infinite sequences.
Паттерны — решения типичных проблем. Средний/сложный уровень.
IIFE для приватности (до ES6 модулей).
const UserModule = (function() { // IIFE - immediate invoke
// Приватные
let users = [];
let apiUrl = 'https://api.example.com';
function validateUser(user) { // Приватный метод
return user.name && user.email;
}
// Публичные
return {
addUser(user) {
if (validateUser(user)) {
users.push(user);
return true;
}
return false;
},
getUsers() {
return [...users]; // Копия для безопасности
},
async fetchUsers() {
try {
let response = await fetch(`${apiUrl}/users`);
if (!response.ok) throw new Error("Fetch error");
users = await response.json();
return users;
} catch (error) {
console.error("Ошибка:", error);
return [];
}
}
};
})();
UserModule.addUser({name: "Анна", email: "anna@test.com"});
console.log(UserModule.getUsers());
UserModule.fetchUsers();
Расширенное: Augmenting modules — добавление методов позже. Альтернатива: ES6 classes с #private.
Для pub/sub (events). Базис для EventEmitter в Node.
class EventEmitter {
constructor() {
this.events = {}; // {event: [listeners]}
}
on(event, listener) { // Subscribe
if (!this.events[event]) this.events[event] = [];
this.events[event].push(listener);
return () => this.off(event, listener); // Возврат unsubscribe
}
off(event, listener) { // Unsubscribe
if (this.events[event]) {
this.events[event] = this.events[event].filter(l => l !== listener);
}
}
emit(event, ...data) { // Publish
if (this.events[event]) {
this.events[event].forEach(listener => listener(...data));
}
}
once(event, listener) { // Один раз
const wrapper = (...args) => {
listener(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
const emitter = new EventEmitter();
const unsubscribe = emitter.on('login', user => {
console.log(`Пользователь ${user.name} вошел`);
});
emitter.once('error', err => console.error(err));
emitter.emit('login', {name: "Анна"});
unsubscribe();
emitter.emit('login', {name: "Боб"}); // Не вызовет
Расширенное (сложный уровень): Namespaces для событий (event:namespace). Wildcards (*.event). Используйте в React (useEffect как observer).
Один экземпляр класса.
class Database {
static instance; // Статическое поле (ES6)
constructor(config) {
if (Database.instance) {
return Database.instance; // Возврат существующего
}
this.connection = this.connect(config);
Database.instance = this;
}
connect(config) {
console.log("Подключаемся...");
return { connected: true, config };
}
query(sql) {
console.log("Запрос:", sql);
return { result: "данные" };
}
}
const db1 = new Database({host: 'localhost'});
const db2 = new Database({host: 'remote'});
console.log(db1 === db2); // true
db1.query("SELECT *");
Расширенное: Lazy init (в constructor). Для многопоточности — не подходит (JS однопоточный). Альтернатива: модули как singletons.
Следуйте стандартам (Airbnb, Google). Используйте linters (ESLint).
Хорошо:
const MAX_RETRIES = 3; // Константы uppercase
const API_URL = 'https://api.example.com'; // 单引号 для строк
function calculateTotal(items) { // camelCase, descriptive
return items.reduce((sum, item) => sum + item.price, 0); // Функциональный стиль
}
class UserService { // PascalCase
constructor(apiClient) {
this.apiClient = apiClient;
}
async getUser(id) {
try {
return await this.apiClient.fetch(`/users/${id}`);
} catch (error) {
console.error('Ошибка загрузки:', error);
throw error;
}
}
}
Плохо:
var maxretries=3; // Нет пробелов, var
var ApiUrl='https://api.example.com';
function calc(items){let s=0;for(let i=0;i<items.length;i++){s+=items[i].price;}return s;} // Не читаемо
Практики: 2-4 пробела, ; в конце, {} для if даже одной строки. Комментарии JSDoc.
Всегда ловите, логируйте, информируйте.
// try-catch в async:
async function fetchData() {
try {
let data = await apiCall();
return process(data);
} catch (error) {
console.error('Ошибка в fetchData:', error.stack); // Stack для debug
showUserMessage('Ошибка загрузки'); // UI
return []; // Graceful degradation
}
}
// Custom errors:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.timestamp = new Date();
}
}
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email обязателен', 'email');
}
}
try {
validateUser({});
} catch (err) {
if (err instanceof ValidationError) {
console.log(`Поле: ${err.field}`);
}
}
// Global handlers:
window.addEventListener('error', event => {
console.error('Глобальная:', event.error);
// Sentry или analytics
});
window.addEventListener('unhandledrejection', event => {
console.error('Promise rejection:', event.reason);
event.preventDefault(); // Не показывать в консоли
});
Расширенное: Error boundaries в React. В Node: process.on('uncaughtException').
Оптимизируйте циклы, DOM, события.
// Циклы: кэш длины.
for (let i = 0, len = array.length; i < len; i++) {} // ХОРОШО
// DOM: batch updates с fragment.
let fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
let div = document.createElement('div');
div.textContent = `Элемент ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // Один reflow
// Debounce: для resize, scroll (throttle - фиксированная частота).
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
console.log('Изменено');
}, 250));
// Throttle:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
Расширенное: Web Workers для тяжелых вычислений. Memoization для функций. Профилируйте с Chrome DevTools.
TypeScript (TS) - это типизированный superset JavaScript, компилируемый в JS. Он добавляет статическую типизацию, интерфейсы, generics и другие фичи для больших проектов. TS помогает ловить ошибки на этапе разработки, улучшает автодополнение и рефакторинг. Установка: npm install -g typescript, компиляция: tsc file.ts. Конфигурация в tsconfig.json.
В TS переменные имеют типы, объявляемые явно. Это предотвращает ошибки типов в runtime.
// Базовые типы: number, string, boolean, null, undefined, any (избегайте, отключает типизацию), unknown (безопасный any).
let age: number = 25; // Явный тип
// age = "двадцать пять"; // Ошибка компиляции: Type 'string' is not assignable to type 'number'.
let name: string = "Анна";
let isStudent: boolean = true;
let nothing: null = null;
let maybe: undefined = undefined;
let flexible: any = 42; // Можно присвоить что угодно, но избегайте
flexible = "текст"; // Нет ошибки
let safe: unknown = 42; // Нужно проверить тип перед использованием
if (typeof safe === "number") {
let sum = safe + 10; // OK
}
// Массивы и кортежи (tuples)
let numbers: number[] = [1, 2, 3]; // Массив чисел
// numbers.push("четыре"); // Ошибка
let tuple: [string, number] = ["Анна", 25]; // Фиксированная длина и типы
// tuple[0] = 30; // Ошибка
Почему типы? В JS типы динамические, что приводит к runtime-ошибкам (например, сложение строки и числа). TS проверяет на compile-time. Лучшая практика: Используйте строгие типы, избегайте any. Для опциональных: let optional?: string;.
Интерфейсы описывают структуру объектов. Union и intersection для комбинаций.
// Интерфейс: контракт для объектов
interface User {
name: string; // Обязательное
age: number;
email?: string; // Опциональное
greet(): string; // Метод
}
let user: User = {
name: "Иван",
age: 30,
greet() {
return `Привет, ${this.name}`;
}
};
// user.age = "тридцать"; // Ошибка
// Type alias: альтернатива интерфейсам для простых типов
type ID = string | number; // Union: строка или число
let userId: ID = "abc123";
userId = 123; // OK
// Intersection: комбинация типов
type Admin = User & { role: string }; // & - intersection
let admin: Admin = {
name: "Админ",
age: 40,
role: "admin",
greet() { return "Привет"; }
};
Расширенное: Extends для наследования интерфейсов: interface Student extends User { major: string; }. Readonly: readonly name: string; для иммутабельности. Ошибки: Несоответствие интерфейсу — compile error.
Типы для параметров и возвращаемого значения. Overloads для перегрузок.
// Базовая функция с типами
function add(a: number, b: number): number { // :number - тип возврата
return a + b;
}
// add("1", 2); // Ошибка: Argument of type 'string' is not assignable to parameter of type 'number'.
// Опциональные и default параметры
function greet(name: string, greeting: string = "Привет"): string {
return `${greeting}, ${name}`;
}
// Rest параметры
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// Arrow функции
const multiply = (a: number, b: number): number => a * b;
// Overloads: несколько сигнатур
function combine(a: number, b: number): number;
function combine(a: string, b: string): string;
function combine(a: any, b: any): any {
return a + b;
}
combine(1, 2); // 3
combine("a", "b"); // "ab"
Расширенное: Типизация callbacks: (callback: (x: number) => void). Async: async function fetchData(): Promise<User[]>. Лучшая практика: Всегда типизируйте функции для ясности.
Классы как в ES6, но с типами, модификаторами доступа (public, private, protected).
class Person {
private name: string; // Private: доступ только внутри класса
public age: number; // Public: по умолчанию
protected nickname: string; // Protected: доступ в классе и наследниках
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Привет, я ${this.name}`;
}
}
class Student extends Person {
major: string;
constructor(name: string, age: number, major: string) {
super(name, age);
this.major = major;
this.nickname = "Студент"; // Доступ к protected
}
greet(): string { // Override с тем же типом
return super.greet() + `, изучаю ${this.major}`;
}
}
let student = new Student("Иван", 20, "Информатика");
console.log(student.greet()); // "Привет, я Иван, изучаю Информатика"
// console.log(student.name); // Ошибка: Property 'name' is private
Расширенное: Abstract классы: abstract class Shape { abstract area(): number; }. Readonly свойства. Ошибки: Несовместимые override — compile error.
Generics для обобщенных типов (как шаблоны в других языках).
// Generic функция: T - placeholder типа
function identity<T>(arg: T): T {
return arg;
}
let num = identity<number>(42); // 42
let str = identity("текст"); // Инференс типа: "текст"
// Generic класс
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
let numberBox = new Box<number>(10);
let stringBox = new Box<string>("коробка");
// Generic интерфейс
interface Pair<K, V> {
key: K;
value: V;
}
let pair: Pair<string, number> = { key: "возраст", value: 25 };
Расширенное: Constraints: <T extends number>. Defaults: <T = string>. Практика: Для коллекций, как Array<T>.
Модули как в ES6, но с типами. Enums, namespaces.
// Enum: перечисления
enum Color {
Red = "Красный",
Green = "Зеленый",
Blue = "Синий"
}
let c: Color = Color.Red; // "Красный"
// Namespaces: группировка
namespace Utils {
export function log(message: string): void {
console.log(message);
}
}
Utils.log("Сообщение");
// Декларации: для внешних библиотек (d.ts файлы)
declare module "some-lib" {
export function func(): void;
}
Расширенное: Utility types: Partial<T>, Readonly<T>, Pick<T, K>. Лучшая практика: Используйте @types для библиотек (npm install @types/node).
Переход от JS к TS: Начните с any, постепенно добавляйте типы. TS улучшает JS-код, но требует компиляции.
Первый проект после основ:
// Простой TODO список
class TodoApp {
constructor() {
this.todos = [];
this.init();
}
init() {
this.form = document.getElementById('todo-form');
this.input = document.getElementById('todo-input');
this.list = document.getElementById('todo-list');
this.form.addEventListener('submit', (e) => {
e.preventDefault();
this.addTodo(this.input.value);
this.input.value = '';
});
}
addTodo(text) {
const todo = {
id: Date.now(),
text: text,
completed: false
};
this.todos.push(todo);
this.render();
}
render() {
this.list.innerHTML = '';
this.todos.forEach(todo => {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox" ${todo.completed ? 'checked' : ''}>
<span>${todo.text}</span>
<button>Удалить</button>
`;
this.list.appendChild(li);
});
}
}
new TodoApp();
Помните: JavaScript учится на практике. Пишите код каждый день, делайте проекты, ошибайтесь и исправляйте ошибки. Удачи! 🚀