JavaScript — язык, с которого начинается путь фронтенд-разработчика. Но «знать синтаксис» и «уметь решать задачи» — это разные вещи. В этом руководстве мы пройдём основы языка через практические задачи: от переменных и типов до классов и наследования.
Каждый раздел — теория + задача с решением. В конце — ссылки на продвинутые темы.
const PI = 3.14; // нельзя переприсвоить
let count = 0; // можно менять
count = 1; // ok
var oldWay = "avoid"; // функциональная область видимостиПравило: всегда используйте const. Меняйте на let только когда значение точно будет меняться. Забудьте про var.
JavaScript «поднимает» объявления переменных в начало области видимости. Но var и let/const ведут себя по-разному:
// var — поднимается и инициализируется как undefined
console.log(x); // undefined (без ошибки!)
var x = 5;
// let/const — поднимаются, но НЕ инициализируются (TDZ)
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;Temporal Dead Zone (TDZ) — период между началом блока и объявлением let/const. В этот период обращение к переменной вызывает ошибку. Это защищает от использования переменной до её объявления.
function example() {
const x = 10;
if (true) {
const y = 20;
console.log(x + y); // 30 — видим x и y
}
console.log(x); // 10 — ok
// console.log(y); // ReferenceError — y не видна
}const и let создают блочную область видимости — переменная видна только в пределах { ... }.var создаёт функциональную область видимости — видна во всей функции.
function demo() {
if (true) {
var a = 1; // видна во всей функции
let b = 2; // видна только в if
}
console.log(a); // 1
// console.log(b); // ReferenceError
}Поменяйте значения двух переменных местами без создания третьей.
let a = 5;
let b = 10;
// Решение с деструктуризацией
[a, b] = [b, a];
console.log(a); // 10
console.log(b); // 5В JavaScript 8 примитивов:
| Тип | Пример | Описание |
|---|---|---|
string | "hello" | строки |
number | 42, 3.14, NaN, Infinity | числа |
bigint | 42n | большие целые числа |
boolean | true, false | логические |
undefined | undefined | не инициализировано |
null | null | пустое значение |
symbol | Symbol("id") | уникальный идентификатор |
typeof "hello"; // "string"
typeof 42; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (исторический баг JS, не исправлен)
typeof Symbol(); // "symbol"
typeof 42n; // "bigint"Почему typeof null === "object"? Это баг первого движка JavaScript. При реализации typeof проверяли внутренний тип объекта, и null имел тот же тип что и объекты. Исправлять не стали — сломает обратную совместимость.
Примитивы хранятся по значению, объекты — по ссылке:
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — a не изменилась
let obj1 = { x: 1 };
let obj2 = obj1; //obj2 ссылается на тот же объект
obj2.x = 99;
console.log(obj1.x); // 99 — obj1 тоже изменилась!Это значит:
// Рекомендуемые способы
typeof value === "string"; // для примитивов
Array.isArray(value); // для массивов
Number.isNaN(value); // для NaN (не isNaN!)
// Избегайте
value instanceof String; // не работает для примитивов
typeof value === "object"; // верно и для null, и для объектовНапишите функцию getType, которая возвращает тип переменной. Для массивов возвращайте "array", для null — "null".
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
getType("hello"); // "string"
getType([1, 2]); // "array"
getType(null); // "null"
getType({}); // "object"Строки в JavaScript неизменяемы — вы не можете изменить отдельный символ:
const s = "Hello, World!";
s.length; // 13
s[0]; // "H"
s.includes("World"); // true
s.indexOf("World"); // 7
s.slice(0, 5); // "Hello"
s.toUpperCase(); // "HELLO, WORLD!"
s.toLowerCase(); // "hello, world!"
s.split(", "); // ["Hello", "World!"]
s.replace("World", "JS"); // "Hello, JS!"
s.trim(); // без пробелов по краям
s.startsWith("Hello"); // true
s.endsWith("!"); // true
s.repeat(2); // "Hello, World!Hello, World!"Шаблонные строки (template literals) — современный способ работы со строками:
const name = "Alice";
const age = 25;
// Интерполяция — подстановка переменных через ${}
const msg1 = `${name} is ${age} years old`;
// Вычисления внутри ${}
const msg2 = `${name} will be ${age + 5} in 5 years`;
// Многострочность
const msg3 = `
Dear ${name},
You are ${age} years old.
Welcome to our platform!
`;// Старый способ (избегайте)
const msg1 = "Hello, " + name + "! You are " + age + " years old";
// Современный способ (используйте)
const msg2 = `Hello, ${name}! You are ${age} years old`;Проверьте, является ли строка палиндромой (читается одинаково в обе стороны, без учёта регистра и пробелов).
function isPalindrome(s) {
const cleaned = s.toLowerCase().replace(/[^a-z0-9]/g, "");
return cleaned === cleaned.split("").reverse().join("");
}
isPalindrome("A man, a plan, a canal: Panama"); // true
isPalindrome("race a car"); // false0.1 + 0.2; // 0.30000000000000004 (проблема точности)
Number.isInteger(1); // true
Number.isInteger(1.5); // false
Math.floor(1.7); // 1 (округление вниз)
Math.ceil(1.2); // 2 (округление вверх)
Math.round(1.5); // 2 (округление до ближайшего)// NaN — результат некорректной операции
0 / 0; // NaN
Number("abc"); // NaN
typeof NaN; // "number" (да, typeof врёт)
NaN === NaN; // false (уникальное свойство!)
// Как проверять NaN
Number.isNaN(NaN); // true — правильный способ
isNaN("abc"); // true (преобразует строку в число)
Number.isNaN("abc"); // false (не преобразует)
// Infinity
1 / 0; // Infinity
-1 / 0; // -InfinityNumber("42"); // 42
Number("abc"); // NaN
parseInt("42px"); // 42 (останавливается на не-цифре)
parseInt("abc42"); // NaN (начинается с не-цифры)
parseFloat("3.14abc"); // 3.14
+"42"; // 42 (унарный плюс)
+""; // 0(3.14159).toFixed(2); // "3.14" (строка!)
(1234567).toLocaleString(); // "1 234 567" (зависит от локали)
(255).toString(16); // "ff" (в шестнадцатеричном)Напишите функцию, вычисляющую факториал числа.
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
factorial(5); // 120
factorial(0); // 1// Всегда используйте === (строгое сравнение)
0 == false; // true (с приведением типов)
0 === false; // false
null == undefined; // true
null === undefined; // falseJavaScript автоматически преобразует значения в boolean. Falsy значения:
false; // логическое false
0; // ноль
(""); // пустая строка
null; // пустое значение
undefined; // не определено
NaN; // не числоВсё остальное — Truthy, включая:
[]; // пустой массив — truthy!
{
} // пустой объект — truthy!
("0"); // строка с нулём — truthy!
new Boolean(false); // объект boolean — truthy!// Осторожно!
if ([]); // true — пустой массив truthy
if ({}); // true — пустой объект truthy
if ("0"); // true — непустая строка truthyconst age = 20;
const status = age >= 18 ? "adult" : "minor";
// Вложенность (избегайте — читаемость падает)
const category = age < 13 ? "child" : age < 18 ? "teen" : "adult";const user = { address: { city: "Moscow" } };
user?.address?.zip; // undefined (без ошибки)
user?.phone?.number; // undefined (без ошибки, даже если phone нет)
null ?? "default"; // "default"
0 ?? "default"; // 0 (отличие от || — ноль не заменяется)
"" ?? "default"; // "" (пустая строка тоже не заменяется)function getDayName(day) {
switch (day) {
case 0:
return "Sunday";
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
default:
return "Invalid day";
}
}Когда использовать: когда сравниваете одно значение с несколькими. Для диапазонов и сложных условий — if/else.
Выведите числа от 1 до 100. Если число делится на 3 — "Fizz", на 5 — "Buzz", на оба — "FizzBuzz".
function fizzBuzz(n) {
const result = [];
for (let i = 1; i <= n; i++) {
if (i % 15 === 0) result.push("FizzBuzz");
else if (i % 3 === 0) result.push("Fizz");
else if (i % 5 === 0) result.push("Buzz");
else result.push(i);
}
return result;
}const arr1 = [1, 2, 3];
const arr2 = new Array(3); // [empty × 3] — не используйте
const arr3 = Array.from("hello"); // ["h", "e", "l", "l", "o"]
const arr4 = Array(3).fill(0); // [0, 0, 0]Мутабельные — изменяют исходный массив:
const arr = [3, 1, 4, 1, 5, 9];
arr.push(2); // добавить в конец → [3, 1, 4, 1, 5, 9, 2]
arr.pop(); // удалить с конца → [3, 1, 4, 1, 5, 9]
arr.unshift(0); // добавить в начало → [0, 3, 1, 4, 1, 5, 9]
arr.shift(); // удалить с начала → [3, 1, 4, 1, 5, 9]
arr.splice(2, 1, 10); // удалить 1 элемент с позиции 2, вставить 10
arr.sort((a, b) => a - b); // сортировка
arr.reverse(); // разворотИммутабельные — возвращают новый массив:
const arr = [3, 1, 4, 1, 5, 9];
arr.slice(1, 4); // [1, 4, 1] — копия
arr.filter((x) => x > 3); // [4, 5, 9] — новый массив
arr.map((x) => x * 2); // [6, 2, 8, 2, 10, 18] — новый массив
arr.concat([7, 8]); // [3, 1, 4, 1, 5, 9, 7, 8] — новый массив
arr.flat(); // [3, 1, 4, 1, 5, 9] — развернуть вложенность
arr.flatMap((x) => [x, x * 2]); // [3, 6, 1, 2, 4, 8, 1, 2, 5, 10, 9, 18]const arr = [3, 1, 4, 1, 5, 9];
// Поиск
arr.indexOf(1); // 1 (первый индекс)
arr.includes(5); // true
arr.find((x) => x > 4); // 5 (первый подходящий)
arr.findIndex((x) => x > 4); // 4 (его индекс)
arr.some((x) => x > 8); // true (хотя бы один)
arr.every((x) => x > 0); // true (все)
// Преобразование
arr.map((x) => x * 2); // [6, 2, 8, 2, 10, 18]
arr.filter((x) => x > 3); // [4, 5, 9]
arr.reduce((sum, x) => sum + x, 0); // 23const arr = [1, 2, 3, 4, 5];
// for...of — можно break/continue
for (const item of arr) {
if (item === 3) break; // остановка
console.log(item); // 1, 2
}
// forEach — нельзя break/continue
arr.forEach((item) => {
// if (item === 3) break; // SyntaxError!
console.log(item);
});
// Рекомендация: используйте for...of когда нужен break
// Для преобразований — map/filter/reduceРеализуйте функцию flatten, которая разворачивает вложенные массивы.
function flatten(arr) {
return arr.reduce((acc, item) => {
return acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
flatten([1, [2, [3, [4]], 5]]); // [1, 2, 3, 4, 5]Сгруппируйте элементы по значению функции.
function groupBy(arr, fn) {
return arr.reduce((groups, item) => {
const key = typeof fn === "function" ? fn(item) : item[fn];
groups[key] = groups[key] || [];
groups[key].push(item);
return groups;
}, {});
}
groupBy([1, 2, 3, 4, 5], (x) => (x % 2 === 0 ? "even" : "odd"));
// { odd: [1, 3, 5], even: [2, 4] }const user = {
name: "Alice",
age: 25,
greet() {
return `Hi, I'm ${this.name}`;
},
};
user.name; // "Alice" (точечная нотация)
user["age"]; // 25 (квадратные скобки — динамические ключи)
user.email = "alice@mail.com"; // добавить свойство
delete user.age; // удалить свойствоОбъекты хранятся по ссылке, поэтому два пустых объекта не равны:
const a = { x: 1 };
const b = { x: 1 };
a === b; // false — разные объекты в памяти
const c = a;
c === a; // true — та же ссылка
c.x = 99;
a.x; // 99 — a тоже изменилась!const user = { name: "Alice", age: 25, city: "Moscow" };
// Object.keys — массив ключей
Object.keys(user); // ["name", "age", "city"]
// Object.values — массив значений
Object.values(user); // ["Alice", 25, "Moscow"]
// Object.entries — массив пар [ключ, значение]
Object.entries(user); // [["name", "Alice"], ["age", 25], ["city", "Moscow"]]
// for...in — перебор ключей (включая унаследованные)
for (const key in user) {
console.log(key, user[key]);
}
// for...of по Object.entries — перебор пар
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}const { name, age, email = "N/A" } = user;
// name = "Alice", age = 25, email = "N/A" (значение по умолчанию)
// Переименование
const { name: userName } = user;
// userName = "Alice"
// Вложенная деструктуризация
const {
address: { city },
} = { address: { city: "Moscow" } };
// city = "Moscow"// Spread — копирование/слияние
const copy = { ...user, age: 30 }; // копия с изменённым age
const merged = { ...obj1, ...obj2 }; // слияние двух объектов
// Rest — сбор оставшихся свойств
const { name: _, ...rest } = user; // rest = { age: 25, city: "Moscow" }Реализуйте глубокое клонирование объекта (без structuredClone).
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
const original = { a: 1, b: { c: 2, d: [3, 4] } };
const cloned = deepClone(original);
cloned.b.c = 99;
console.log(original.b.c); // 2 — оригинал не изменилсяПреобразуйте объект в строку и обратно (без JSON.stringify/parse).
function serialize(obj) {
if (typeof obj === "string") return `"${obj}"`;
if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
if (obj === null) return "null";
if (Array.isArray(obj)) {
return `[${obj.map(serialize).join(",")}]`;
}
const props = Object.entries(obj)
.map(([k, v]) => `"${k}":${serialize(v)}`)
.join(",");
return `{${props}}`;
}
serialize({ name: "Alice", scores: [1, 2, 3] });
// '{"name":"Alice","scores":[1,2,3]}'// Объявление функции (function declaration)
function add(a, b) {
return a + b;
}
// Поднимается (hoisting) — можно вызвать до объявления
// Выражение функции (function expression)
const multiply = function (a, b) {
return a * b;
};
// Не поднимается — нельзя вызвать до объявления
// Стрелочная функция (arrow function)
const divide = (a, b) => a / b;
// Нет своего this, нет arguments
// IIFE (Immediately Invoked Function Expression)
const result = (function () {
return 42;
})();
// Вызывается сразу после объявленияconst obj = {
name: "Alice",
// Обычная функция — this = obj
greetRegular() {
return `Hello, ${this.name}`;
},
// Стрелочная функция — this = внешний контекст (не obj!)
greetArrow: () => {
return `Hello, ${this.name}`; // this = undefined (или window)
},
};
obj.greetRegular(); // "Hello, Alice"
obj.greetArrow(); // "Hello, undefined"Правило: используйте стрелочные функции для колбэков и коротких выражений. Обычные — для методов объектов и когда нужен this.
// 1. Глобальный вызов
function show() {
console.log(this);
}
show(); // window (или undefined в strict mode)
// 2. Метод объекта
const obj = {
show() {
console.log(this);
},
};
obj.show(); // obj
// 3. Конструктор
function User(name) {
this.name = name;
}
new User("Alice"); // новый объект
// 4. call/apply/bind
function greet() {
console.log(this.name);
}
greet.call({ name: "Bob" }); // "Bob"Функция «запоминает» переменные из внешней области видимости:
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count,
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2Подробнее — в статье Замыкания в JavaScript.
function greet(name = "Guest") {
return `Hello, ${name}`;
}
greet(); // "Hello, Guest"
greet("Bob"); // "Hello, Bob"function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10Реализуйте функцию curry, которая превращает функцию с несколькими аргументами в цепочку вызовов.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...args2) => curried(...args, ...args2);
};
}
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6try {
const data = JSON.parse("invalid json");
} catch (error) {
// Выполняется при ошибке
console.error(error.message); // "Unexpected token i in JSON at position 0"
console.error(error.name); // "SyntaxError"
} finally {
// Выполняется всегда (с ошибкой или без)
console.log("Всегда выполняется");
}// ReferenceError — обращение к несуществующей переменной
console.log(x); // ReferenceError: x is not defined
// TypeError — неправильный тип
null.foo; // TypeError: Cannot read properties of null
(5).foo(); // TypeError: (5).foo is not a function
// SyntaxError — синтаксическая ошибка
eval("if (true) {"); // SyntaxError
// RangeError — значение за пределами допустимого
(1).toFixed(100); // RangeErrorfunction divide(a, b) {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
// Генерация с кастомным именем
function validateAge(age) {
if (age < 0) {
const err = new Error("Age cannot be negative");
err.name = "ValidationError";
throw err;
}
}
try {
divide(10, 0);
} catch (e) {
console.error(e.message); // "Division by zero"
}try/catch — для операций, которые могут завершиться ошибкой (парсинг, запросы, файлы)throw — когда функция не может выполнить свою задачуОберните JSON.parse в функцию, которая возвращает null вместо ошибки.
function safeJsonParse(str) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
safeJsonParse('{"a": 1}'); // { a: 1 }
safeJsonParse("invalid"); // nullPromise — объект, представляющий результат асинхронной операции. Три состояния:
pending — ожиданиеfulfilled — успешноrejected — ошибкаconst promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done"), 1000);
});
promise
.then((result) => console.log(result))
.catch((err) => console.error(err));async/await — современный способ работы с промисами. Код выглядит синхронным, но работает асинхронно.
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
} catch (err) {
console.error(err);
}
}Правило: await можно использовать только внутри async функции (или в модулях).
// С .then()
fetchUser(id)
.then((user) => fetchPosts(user.id))
.then((posts) => console.log(posts))
.catch((err) => console.error(err));
// С async/await
async function getUserPosts(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
console.log(posts);
} catch (err) {
console.error(err);
}
}
// Рекомендация: async/await для сложных цепочек, .then() для простых// Все параллельно (Promise.all)
const [users, posts] = await Promise.all([
fetch("/users").then((r) => r.json()),
fetch("/posts").then((r) => r.json()),
]);
// Первый успешный (Promise.any)
const fastest = await Promise.any([
fetch("/mirror1"),
fetch("/mirror2"),
fetch("/mirror3"),
]);
// Все с результатами (Promise.allSettled)
const results = await Promise.allSettled([
fetch("/might-fail-1"),
fetch("/might-fail-2"),
]);
// results = [{ status: "fulfilled", value: ... }, { status: "rejected", reason: ... }]console.log("1"); // синхронный код
setTimeout(() => console.log("2"), 0); // макрозадача
Promise.resolve().then(() => console.log("3")); // микрозадача
console.log("4"); // синхронный код
// Вывод: 1, 4, 3, 2Порядок выполнения:
Создайте функцию delay, которая возвращает промис, резолвящийся через ms миллисекунд.
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function example() {
console.log("Start");
await delay(1000);
console.log("After 1 second");
}Реализуйте throttle — функцию, которая вызывается не чаще чем раз в limit мс.
function throttle(fn, limit) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
return fn.apply(this, args);
}
};
}
const throttledLog = throttle(console.log, 1000);
throttledLog("a"); // выведет сразу
throttledLog("b"); // проигнорированоЗапросите данные о пользовате, затем о его постах, затем о комментариях к первому посту.
async function getUserPostComments(userId) {
const user = await fetch(`/api/users/${userId}`).then((r) => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then((r) => r.json());
const comments = await fetch(`/api/posts/${posts[0].id}/comments`).then((r) =>
r.json(),
);
return { user, post: posts[0], comments };
}Повторяйте запрос до maxRetries раз с увеличением задержки.
async function retry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (i === maxRetries - 1) throw err;
await delay(1000 * Math.pow(2, i));
}
}
}
// Использование
const data = await retry(() => fetch("/api/unstable").then((r) => r.json()));class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I'm ${this.name}, ${this.age} years old`;
}
static create(name, age) {
return new User(name, age);
}
}
const user = new User("Alice", 25);
user.greet(); // "Hi, I'm Alice, 25 years old"
const user2 = User.create("Bob", 30); // статический методthis в классах — это экземпляр класса. Но есть нюанс со стрелочными методами:
class Timer {
seconds = 0;
// Проблема: this теряется при передаче метода
startBad() {
setInterval(function () {
this.seconds++; // this = undefined (или window)
console.log(this.seconds);
}, 1000);
}
// Решение 1: стрелочное свойство (создаётся в constructor)
startGood = () => {
setInterval(() => {
this.seconds++; // this = экземпляр Timer
console.log(this.seconds);
}, 1000);
};
}class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} purrs`;
}
}
new Dog("Rex").speak(); // "Rex barks"
new Cat("Whiskers").speak(); // "Whiskers purrs"class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) this.#balance += amount;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
account.balance; // 1500
// account.#balance; // SyntaxErrorclass Temperature {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
get fahrenheit() {
return this.#celsius * 1.8 + 32;
}
set fahrenheit(f) {
this.#celsius = (f - 32) / 1.8;
}
}
const t = new Temperature(100);
t.fahrenheit; // 212
t.fahrenheit = 32;
t.fahrenheit; // 32Методы классов хранятся в прототипе, а не в каждом экземпляре:
class Dog {
constructor(name) {
this.name = name; // хранится в экземпляре
}
bark() {
return `${this.name} barks`;
}
}
const dog1 = new Dog("Rex");
const dog2 = new Dog("Buddy");
// bark() одна на всех — в прототипе
Dog.prototype.bark === dog1.bark; // true
// name разные — в экземплярах
dog1.name; // "Rex"
dog2.name; // "Buddy"class Animal {}
class Dog extends Animal {}
const dog = new Dog();
dog instanceof Dog; // true
dog instanceof Animal; // true
dog.constructor === Dog; // trueclass EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set());
}
this.#listeners.get(event).add(callback);
return () => this.off(event, callback);
}
off(event, callback) {
this.#listeners.get(event)?.delete(callback);
}
emit(event, ...args) {
this.#listeners.get(event)?.forEach((cb) => cb(...args));
}
}
const emitter = new EventEmitter();
const unsub = emitter.on("data", (msg) => console.log(msg));
emitter.emit("data", "hello"); // "hello"
unsub(); // отписка
emitter.emit("data", "hello"); // ничего не выведетconst [first, , third, ...rest] = [1, 2, 3, 4, 5];
// first = 1, third = 3, rest = [4, 5]
// Обмен переменных
let x = 1,
y = 2;
[x, y] = [y, x];const { name, age, ...rest } = { name: "Alice", age: 25, city: "Moscow" };
// rest = { city: "Moscow" }
// Переименование
const { name: userName } = { name: "Alice" };
// userName = "Alice"function log(a, b, c) {
console.log(a, b, c);
}
const args = [1, 2, 3];
log(...args); // 1 2 3// math.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}
export default class Calculator {
/* ... */
}
// app.js
import Calculator, { PI, add } from "./math.js";const module = await import("./heavy-module.js");
module.doSomething();| Раздел | Ключевые концепции | Что запомнить |
|---|---|---|
| Переменные | const, let, var | const по умолчанию, let когда меняется, var избегать |
| Типы данных | 8 примитивов, typeof | typeof null === "object" — баг, NaN !== NaN |
| Строки | Методы, шаблонные строки | Строки неизменяемы, шаблоны через ${} |
| Числа | Точность, NaN, Infinity | Number.isNaN(), не isNaN() |
| Условия | Truthy/falsy, сравнение | Всегда ===, пустой массив — truthy |
| Массивы | Мутабельные/иммутабельные | splice/sort/reverse меняют, slice/map/filter нет |
| Объекты | Ссылки, деструктуризация | { a: 1 } !== { a: 1 }, spread для копирования |
| Функции | this, стрелочные, замыкание | Стрелочные не имеют своего this |
| Ошибки | try/catch, типы ошибок | ReferenceError, TypeError, SyntaxError |
| Асинхронность | Promise, async/await, event loop | Микрозадачи → макрозадачи |
| Классы | Наследование, приватные поля | #balance, extends, instanceof |
Это основы. Дальше — более сложные темы, которые требуют практики:
Попробуйте решить задачи на Девстанции — AI проверит ваш код и покажет, как написать его лучше.