Skip to content

JavaScript Шпаргалка

Язык веба — фронтенд, бэкенд и далее.

01

Начало работы

Hello World и комментарии

JavaScript работает в браузерах и Node.js. console.log() — основной вывод для отладки. JSDoc-комментарии (/** */) предоставляют информацию о типах и документацию для IDE. Используйте 'use strict' или ES-модули для более безопасного парсинга. Комментарии игнорируются во время выполнения.

javascript
// Single-line comment
/* Multi-line
   comment */

// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);

// JSDoc comments (for documentation)
/**
 * Adds two numbers
 * @param {number} a - First number
 * @param {number} b - Second number
 * @returns {number} The sum
 */
function add(a, b) { return a + b; }

Strict Mode и модули

'use strict' включает более строгий парсинг, ловя тихие ошибки. ES-модули (import/export) — современный стандарт; CommonJS (require/module.exports) — традиционный Node.js. Всегда используйте модули во избежание загрязнения глобальной области видимости. Браузеры поддерживают <script type='module'>.

javascript
"use strict";  // enables strict mode (catches common mistakes)
x = 10;  // ReferenceError: x is not defined (strict mode)

// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }

// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };

// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.ts

Переменные: let, const, var

Всегда предпочитайте const; используйте let только при необходимости переназначения; избегайте var полностью. const предотвращает переназначение, но не мутацию — содержимое объектов/массивов всё ещё может меняться. let/const имеют блочную область видимости; var имеет функциональную область видимости и hoisting (вызывая баги). TDZ предотвращает использование переменных до объявления.

javascript
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob";  // OK - object contents can change
// user = {};  // TypeError - can't reassign const

// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
  let local = 5;  // only accessible in this block
}

// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy";  // hoisted, can cause bugs

// Temporal Dead Zone (TDZ)
// console.log(x);  // ReferenceError
// let x = 5;

Типы данных и typeof

В JavaScript 7 примитивных типов (string, number, bigint, boolean, undefined, null, symbol) и ссылочные типы (объекты, массивы, функции). Примитивы неизменяемы и копируются по значению; объекты изменяемы и передаются по ссылке. typeof null возвращает 'object' из-за исторического бага — используйте === null для проверки на null.

javascript
// Primitive types (immutable, passed by value)
const str = "hello";        // string
const num = 42;             // number (no separate int/float)
const big = 9007199254740993n;  // bigint
const bool = true;          // boolean
const undef = undefined;    // undefined (no value)
const empty = null;         // null (intentional empty)
const sym = Symbol("id");   // symbol (unique identifier)

// Reference types (mutable, passed by reference)
const arr = [1, 2, 3];      // object (array)
const obj = { a: 1 };       // object
const fn = function() {};   // object (function)

// typeof operator
console.log(typeof "hi");      // "string"
console.log(typeof 42);        // "number"
console.log(typeof true);      // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null);      // "object" (historical bug!)
console.log(typeof []);        // "object"
console.log(typeof {});        // "object"
console.log(typeof function(){}); // "function"

Преобразование и приведение типов

Приведение типов в JavaScript известно своей запутанностью. Всегда используйте === (строгое равенство) вместо == (нестрогое) для избежания неожиданного приведения. Оператор + конкатенирует, если любой операнд строка; другие операторы приводят к числу. Ложные значения: false, 0, '', null, undefined, NaN. Используйте Number.isNaN() для проверки на NaN (NaN !== NaN).

javascript
// Explicit conversion
String(42);        // "42"
(42).toString();   // "42"
Number("42");      // 42
Number("3.14");    // 3.14
Number("");        // 0
Number("abc");     // NaN
parseInt("42px");  // 42
parseFloat("3.14abc");  // 3.14
Boolean(0);        // false
Boolean("");       // false
Boolean("x");      // true

// Implicit coercion (often confusing)
console.log("5" + 3);   // "53" (string concatenation)
console.log("5" - 3);   // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1);  // 2

// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy");  // runs! non-empty string

// Strict vs loose equality
console.log(1 == "1");   // true (loose, coerces)
console.log(1 === "1");  // false (strict, no coercion)
console.log(null == undefined);  // true
console.log(null === undefined); // false
02

Строки

Строковые методы

Строки неизменяемы — методы возвращают новые строки. slice() поддерживает отрицательные индексы (с конца); substring() — нет. replace() заменяет только первое совпадение; используйте replaceAll() (ES2021) для всех. at() (ES2022) допускает отрицательные индексы. split() + join() — идиоматичный способ заменить символы в строке.

javascript
const s = "Hello, World";

// Length & access
console.log(s.length);     // 12
console.log(s[0]);         // "H"
console.log(s.charAt(0));  // "H"
console.log(s.at(-1));     // "d" (ES2022, negative index)

// Case
console.log(s.toUpperCase());  // "HELLO, WORLD"
console.log(s.toLowerCase());  // "hello, world"

// Search
console.log(s.indexOf("World"));   // 7 (-1 if not found)
console.log(s.includes("World"));  // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World"));   // true

// Extract
console.log(s.slice(0, 5));    // "Hello"
console.log(s.slice(-5));      // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", "));    // ["Hello", "World"]

// Modify
console.log(s.replace("o", "0"));   // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim());   // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"

Шаблонные литералы

Шаблонные литералы (обратные кавычки) допускают интерполяцию строк с ${}, многострочные строки и тегированные шаблоны. Они гораздо читабельнее конкатенации строк. Тегированные шаблоны позволяют обрабатывать шаблонные литералы функцией — используется styled-components, graphql-tag и т.д.

javascript
const name = "Alice";
const age = 30;

// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);

// Multi-line strings
const html = `
  <div>
    <h1>${name}</h1>
    <p>Age: ${age}</p>
  </div>
`;

// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);

// Tagged templates
function highlight(strings, ...values) {
  return strings.reduce((acc, str, i) =>
    acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"

Поиск в строках и Regex

Строки JavaScript поддерживают regex через match(), matchAll(), replace(), search() и split(). Именованные группы захвата (?<name>...) (ES2018) делают regex читабельнее. matchAll() возвращает итератор (эффективнее match() для глобального regex). Используйте .test() для проверки совпадения шаблона без извлечения.

javascript
const text = "The quick brown fox jumps over the lazy dog";

// Search methods
console.log(text.search(/brown/));  // 10 (index of match)
console.log(text.match(/\w+/g));   // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches

// Replace with regex
console.log(text.replace(/o/g, "0"));  // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!"));  // capture group

// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day);  // 2024 01 15

// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year);   // 2024
console.log(m.groups.month);  // 01

// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]"));  // true

Итерация строк и spread

Строки итерируемы через for...of. Оператор spread [...] разделяет строку на символы — необходим для корректной обработки эмодзи (суррогатные пары). Сравнение строк лексикографическое по кодовым единицам UTF-16, поэтому используйте localeCompare() для локализованной сортировки. Эмодзи и некоторые символы длиной в 2 кодовые единицы.

javascript
const s = "Hello";

// Iterate characters
for (const char of s) {
  console.log(char);  // H, e, l, l, o
}

// Spread into array
const chars = [...s];  // ["H", "e", "l", "l", "o"]
console.log(chars);

// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper);  // HELLO

// String comparison
console.log("a" < "b");   // true (lexicographic)
console.log("apple" < "banana");  // true
console.log("Z" < "a");   // true (uppercase < lowercase in ASCII)

// Locale-aware comparison
console.log("ö".localeCompare("o", "de"));  // locale-specific

// Repeat
console.log("ab".repeat(3));  // "ababab"

// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length);        // 2 (surrogate pair!)
console.log([...emoji].length);   // 1 (correct)
console.log("😀".codePointAt(0)); // 128512
03

Числа и математика

Числа и операторы

В JavaScript один числовой тип (64-битный float) — нет отдельного int/float. BigInt (суффикс n) обрабатывает целые числа за пределами 2^53. Арифметика с плавающей точкой имеет проблемы точности (0.1 + 0.2 !== 0.3) — используйте Number.EPSILON для сравнений. ** — оператор возведения в степень (ES2016).

javascript
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6;      // 1000000
const hex = 0xff;     // 255
const bin = 0b1010;   // 10
const oct = 0o755;    // 493
const big = 9007199254740993n;  // BigInt (arbitrary precision)

// Arithmetic
console.log(10 / 3);    // 3.333...
console.log(10 % 3);    // 1 (remainder)
console.log(2 ** 10);   // 1024 (exponent)
console.log(Math.floor(10 / 3));  // 3
console.log(Math.trunc(-3.7));    // -3 (toward zero)

// Increment/decrement
let x = 5;
console.log(x++);  // 5 (post-increment, returns old)
console.log(++x);  // 7 (pre-increment, returns new)

// Floating point issues
console.log(0.1 + 0.2);          // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);  // true

Объект Math

Объект Math предоставляет константы и функции. Все тригонометрические функции используют радианы. Math.random() возвращает [0, 1) — умножайте и округляйте для целых диапазонов. Для криптографической случайности используйте crypto.getRandomValues(). Math.max/min не принимают массивы напрямую — разворачивайте через ....

javascript
// Constants
console.log(Math.PI);       // 3.141592653589793
console.log(Math.E);        // 2.718281828459045
console.log(Math.SQRT2);    // 1.4142135623730951

// Rounding
console.log(Math.round(3.7));   // 4 (nearest)
console.log(Math.floor(3.7));   // 3 (down)
console.log(Math.ceil(3.2));    // 4 (up)
console.log(Math.trunc(-3.7));  // -3 (toward zero)
console.log(Math.sign(-5));     // -1 (sign: -1, 0, or 1)

// Power & roots
console.log(Math.pow(2, 10));   // 1024
console.log(Math.sqrt(144));    // 12
console.log(Math.cbrt(27));     // 3
console.log(Math.abs(-5));      // 5

// Min/Max
console.log(Math.max(1, 5, 3));       // 5
console.log(Math.min(1, 5, 3));       // 1
console.log(Math.max(...[1, 5, 3]));  // 5 (spread array)

// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2));   // 1
console.log(Math.cos(0));             // 1
console.log(Math.PI / 180 * 90);      // radians from degrees

// Random
console.log(Math.random());           // 0 to <1
console.log(Math.floor(Math.random() * 100));  // 0-99 integer

Методы чисел и парсинг

Всегда указывайте основание (base) для parseInt() — старые браузеры интерпретируют ведущие нули как восьмеричные. Number.isNaN() надёжна; глобальная isNaN() приводит (isNaN('abc') — true). toFixed() возвращает строку, а не число. Числа за MAX_SAFE_INTEGER теряют точность — используйте BigInt.

javascript
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2));      // "1234.57" (string)
console.log(num.toPrecision(3));  // "1.23e+3"
console.log(num.toString(2));     // binary string
console.log((255).toString(16));  // "ff" (hex)

// Number object methods
console.log(Number.isInteger(42));      // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN));         // true (reliable)
console.log(Number.isNaN("NaN"));       // false (global isNaN would be true)
console.log(Number.parseInt("42px"));   // 42
console.log(Number.parseFloat("3.14")); // 3.14

// Parsing strings
console.log(parseInt("42", 10));   // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14

// Safe integers
console.log(Number.MAX_SAFE_INTEGER);  // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53));  // false

// NaN checks
const result = Number("abc");  // NaN
console.log(Number.isNaN(result));  // true
console.log(result === NaN);        // false! NaN !== NaN
04

Структуры данных

Массивы

Массивы динамические, упорядоченные и могут содержать смешанные типы. push/pop — O(1); shift/unshift/splice — O(n). find()/findIndex() принимают функцию-предикат. forEach() ничего не возвращает; используйте map() для преобразования. Массивы — объекты — typeof [] равно 'object'. Используйте Array.isArray() для проверки.

javascript
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5);  // [empty x 5]

// Access & modify
console.log(nums[0]);      // 1
console.log(nums.length);  // 5
nums[0] = 0;               // modify
console.log(nums.at(-1));  // 5 (negative index, ES2022)

// Add/remove
nums.push(6);          // add to end, returns new length
nums.pop();            // remove from end, returns element
nums.unshift(0);       // add to start
nums.shift();          // remove from start
nums.splice(1, 2);     // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1

// Search
console.log(nums.indexOf(3));     // index or -1
console.log(nums.includes(3));    // true/false
console.log(nums.find(n => n > 3));    // first match
console.log(nums.findIndex(n => n > 3)); // index of first match

// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
  console.log(idx, val);
}

Методы массивов (map, filter, reduce)

map/filter/reduce — святая троица функционального программирования массивов. map преобразует, filter отбирает, reduce агрегирует. Они цепочечные и не мутируют исходный (кроме reverse/sort). sort() по умолчанию преобразует в строки — всегда предоставляйте компаратор для чисел. flat() выравнивает вложенные массивы.

javascript
const nums = [1, 2, 3, 4, 5];

// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2);       // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);

// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]

// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0);  // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));

// Chaining
const result = nums
  .filter(n => n % 2 === 0)   // [2, 4]
  .map(n => n * 10)           // [20, 40]
  .reduce((sum, n) => sum + n, 0); // 60

// Other useful methods
console.log(nums.slice(1, 3));   // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]);     // spread (modern)
console.log(nums.reverse());      // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort());   // [1, 2, 3] (string sort!)
console.log(nums.flat());        // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]

Объекты

Объекты — коллекции ключ-значение (ключи строки или символы). Точечная нотация для статических ключей, скобочная — для динамических/специальных. Вычисляемые имена свойств {[expr]: val} — ES6. Object.keys/values/entries извлекают массивы; Object.fromEntries обращает entries. for...in итерирует ключи (включая унаследованные).

javascript
// Object literal
const user = {
  name: "Alice",
  age: 30,
  "is-admin": false,  // keys with special chars need quotes
  greet() {           // method shorthand
    return `Hello, I'm ${this.name}`;
  }
};

// Access
console.log(user.name);       // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)

// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };

// Add/modify/delete
user.email = "[email protected]";  // add
user.age = 31;            // modify
delete user["is-admin"];  // delete

// Check property existence
console.log("name" in user);          // true
console.log(user.hasOwnProperty("name")); // true

// Iterate
for (const key in user) {
  console.log(key, user[key]);
}

// Object methods
console.log(Object.keys(user));    // ["name", "age", ...]
console.log(Object.values(user));  // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}

Деструктуризация и spread

Деструктуризация лаконично извлекает значения из объектов/массивов. Поддерживает переименование (key: newName), значения по умолчанию (= value) и rest (...rest). Spread (...) разворачивает итерируемые объекты/объекты — отлично для слияния и поверхностного копирования. Object spread переопределяет дубликаты ключей (последний выигрывает). Деструктуризация в параметрах функций мощна для опциональных конфигов.

javascript
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user;          // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user;            // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}

// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3];  // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]

// Swap variables
let x = 1, y = 2;
[x, y] = [y, x];  // x=2, y=1

// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];

// Function parameters
function greet({ name, greeting = "Hello" }) {
  return `${greeting}, ${name}`;
}
greet({ name: "Alice" });  // "Hello, Alice"

// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2];  // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 };  // {a:1, b:2}
const clone = { ...user };  // shallow copy

Map, Set, WeakMap

Map допускает любой тип ключа (объекты, не только строки) и сохраняет порядок вставки — в отличие от обычных объектов. Set хранит уникальные значения — идеально для дедупликации. Ключи WeakMap/WeakSet слабо ссылаются (могут быть собраны сборщиком мусора), предотвращая утечки памяти. Используйте Map, когда нужны нестроковые ключи или частые добавления/удаления.

javascript
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");

console.log(map.get("name"));    // "Alice"
console.log(map.has("name"));    // true
console.log(map.size);           // 3
map.delete(42);
map.clear();

// Iterate Map
for (const [key, value] of map) {
  console.log(key, value);
}

// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size);  // 3 (duplicates removed)
set.add(4);
set.has(2);  // true
set.delete(1);

// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]);        // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x)));  // {1}

// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value");  // key can be garbage collected
05

Управление потоком

If / Else и тернарный оператор

Используйте if/else для сложной логики; тернарный — для простого выбора значения. && и || коротко замыкаются (полезно для значений по умолчанию/условий). ?? (nullish coalescing) проверяет только null/undefined, в отличие от ||, проверяющего все ложные значения. ?. (optional chaining) безопасно обращается к вложенным свойствам без ошибок.

javascript
const score = 85;

// if / else if / else
if (score >= 90) {
  console.log("A");
} else if (score >= 80) {
  console.log("B");
} else {
  console.log("C");
}

// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";

// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
             : score >= 80 ? "good"
             : score >= 60 ? "pass"
             : "fail";

// Short-circuit evaluation
const name = user?.name || "Anonymous";  // default value
const value = condition && doSomething(); // execute if true

// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10;  // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default";  // "default"

// Optional chaining (?.)
const city = user?.address?.city;  // undefined if any link is null
const length = user?.name?.length;  // undefined if user or name is null

Оператор switch

switch сравнивает со строгим равенством (===), поэтому тип важен. Не забывайте break — без него выполнение проваливается в следующий case. Группируйте case, складывая их (case 6: case 7:). Switch чище длинных цепочек if/else для дискретных значений. Современный код иногда предпочитает таблицы поиска в объектах.

javascript
const day = 3;

// Traditional switch (use break!)
switch (day) {
  case 1:
    console.log("Monday");
    break;  // without break, falls through!
  case 2:
    console.log("Tuesday");
    break;
  case 6:
  case 7:  // multiple cases share code
    console.log("Weekend");
    break;
  default:
    console.log("Weekday");
}

// Switch with return (no break needed)
function getColor(type) {
  switch (type) {
    case "success": return "green";
    case "error": return "red";
    case "warning": return "yellow";
    default: return "gray";
  }
}

// Strict equality (===)
switch (1) {
  case "1": console.log("string");  // NOT matched
  case 1: console.log("number");    // matched
}

Циклы

Используйте for...of для массивов/строк (значения), for...in для объектов (ключи) — никогда for...in для массивов (итерирует индексы как строки и включает прототип). while проверяет до выполнения; do...while выполняется хотя бы раз. break выходит, continue пропускает. Используйте .entries() для получения индекс+значение через for...of.

javascript
// for loop
for (let i = 0; i < 5; i++) {
  console.log(i);
}

// for...of (iterable values - arrays, strings, maps)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
  console.log(fruit);
}

// for...in (object keys - AVOID for arrays!)
const user = { name: "Alice", age: 30 };
for (const key in user) {
  console.log(key, user[key]);
}

// while
let count = 0;
while (count < 3) {
  console.log(count);
  count++;
}

// do...while (runs at least once)
let i = 0;
do {
  console.log(i);
  i++;
} while (i < 3);

// break & continue
for (let i = 0; i < 10; i++) {
  if (i === 5) break;      // exit loop
  if (i % 2 === 0) continue; // skip iteration
  console.log(i);
}

// Iterate with index
for (const [index, value] of fruits.entries()) {
  console.log(index, value);
}

Итераторы и генераторы

Итераторы реализуют next(), возвращающий {value, done}. Генераторы (function*) упрощают создание итераторов через yield — они приостанавливают выполнение и возобновляются при next(). Генераторы ленивые (вычисляют по требованию) и могут быть бесконечными. Используйте для пользовательских итерируемых объектов, последовательностей и async-потоков.

javascript
// Iterable protocol (Symbol.iterator)
const range = {
  from: 1, to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      }
    };
  }
};

for (const num of range) console.log(num);  // 1, 2, 3, 4, 5
console.log([...range]);  // [1, 2, 3, 4, 5]

// Generator function (function*)
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const gen = idGenerator();
console.log(gen.next());  // {value: 1, done: false}
console.log(gen.next());  // {value: 2, done: false}
console.log(gen.next().value);  // 3

// Generator with yield*
function* nested() {
  yield 1;
  yield* [2, 3, 4];  // delegate to another iterable
  yield 5;
}
06

Функции

Объявления и выражения функций

Объявления функций поднимаются (можно вызвать до определения); выражения — нет. Стрелочные функции лаконичны и не имеют собственного 'this' (наследуют из объемлющей области). IIFE создают приватные области (менее нужны с модулями). Функции первого класса — передавайте как аргументы, возвращайте, храните в переменных.

javascript
// Function declaration (hoisted - can be called before definition)
greet("Alice");  // works (hoisted)
function greet(name) {
  return `Hello, ${name}!`;
}

// Function expression (not hoisted)
const greet2 = function(name) {
  return `Hi, ${name}!`;
};

// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
};

// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); };  // block body needs return

// IIFE (Immediately Invoked Function Expression)
const result = (function() {
  const private = "secret";
  return private.toUpperCase();
})();

// Functions are first-class (can be passed/returned)
function apply(fn, value) {
  return fn(value);
}
console.log(apply(square, 5));  // 25

Стрелочные функции и this

Стрелочные функции не имеют собственных 'this', 'arguments', 'super' или 'new.target' — они наследуют из объемлющей области видимости. Это делает их идеальными для callback'ов (особенно в методах класса). Но их нельзя использовать как конструкторы или методы, нуждающиеся в собственном 'this'. Используйте обычные функции для методов объектов.

javascript
// Arrow function variations
const add = (a, b) => a + b;           // implicit return
const greet = name => `Hi ${name}`;    // single param, no parens
const log = () => console.log("hi");   // no params
const obj = (x, y) => ({ x, y });      // return object needs parens
const multi = (a, b) => {              // block body
  const sum = a + b;
  return sum * 2;
};

// Arrow functions don't have their own 'this'
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++;  // 'this' refers to Timer instance
    console.log(this.seconds);
  }, 1000);
}

// Regular function has its own 'this'
function Counter() {
  this.count = 0;
  // Regular function: 'this' is undefined (strict) or global
  document.addEventListener("click", function() {
    // this.count++;  // ERROR: this is not Counter
  });
  // Arrow function: 'this' is Counter
  document.addEventListener("click", () => {
    this.count++;  // works!
  });
}

// Arrow functions can't be constructors
// const obj = new arrowFunc();  // TypeError

Замыкания

Замыкания — функции, которые 'запоминают' переменные из определяющей области видимости даже после её выхода. Они обеспечивают приватность данных (паттерн модуля), мемоизацию, каррирование и частичное применение. Каждая функция в JavaScript — замыкание. Внутренняя функция хранит ссылку на внешние переменные, а не копию.

javascript
// A closure is a function that remembers its outer variables
function makeCounter() {
  let count = 0;  // private variable
  return function() {
    return ++count;
  };
}

const counter = makeCounter();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3
// count is private - can't access directly

// Module pattern (pre-ES6)
const calculator = (function() {
  const result = 0;  // private
  return {
    add(x) { return result + x; },
    multiply(x) { return result * x; }
  };
})();

// Practical: memoization
function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = fn.apply(this, args);
    }
    return cache[key];
  };
}

const slowFib = memoize(n =>
  n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);

// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5));  // 10

Аргументы и rest/spread

Параметры по умолчанию предоставляют запасные значения. Rest-параметры (...name) собирают дополнительные аргументы в настоящий массив — предпочитайте устаревшему объекту 'arguments'. Spread (...) разворачивает массив в отдельные аргументы. Деструктуризация в параметрах включает именованные опциональные конфиг-объекты — распространённый API-паттерн.

javascript
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
  return `${greeting}, ${name}!`;
}
greet();              // "Hello, Guest!"
greet("Alice");       // "Hello, Alice!"
greet("Bob", "Hi");   // "Hi, Bob!"

// Rest parameters (...args collects into array)
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));  // 10

function log(tag, ...args) {
  console.log(tag, args);
}

// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums));  // 3 (spread array as args)
console.log(sum(...nums));       // 6

// arguments object (old way, avoid in modern code)
function old() {
  console.log(arguments);  // array-like, not a real array
  const args = Array.from(arguments);  // convert
}

// Destructuring parameters
function process({ name, age = 0 } = {}) {
  console.log(name, age);
}
process({ name: "Alice" });  // Alice 0
process();  // undefined 0 (default empty object)
07

ООП и классы

Классы и конструктор

ES6-классы — синтаксический сахар над прототипами. Приватные поля (#name) — ES2022 и действительно приватные (в отличие от соглашения _name). Геттеры/сеттеры позволяют вычисляемые свойства. Статические члены принадлежат классу, а не экземплярам. Поля класса (name = value) инициализируют свойства экземпляра без конструктора.

javascript
class Person {
  // Fields (class fields proposal - ES2022)
  species = "human";  // instance field
  
  // Private fields (ES2022)
  #ssn = "secret";    // truly private
  
  // Static field/method
  static count = 0;
  
  // Constructor
  constructor(name, age) {
    this.name = name;
    this.age = age;
    Person.count++;
  }
  
  // Instance method
  greet() {
    return `Hello, I'm ${this.name}`;
  }
  
  // Getter
  get info() {
    return `${this.name}, ${this.age}`;
  }
  
  // Setter
  set age(value) {
    if (value < 0) throw new Error("Invalid age");
    this._age = value;
  }
  
  get age() {
    return this._age;
  }
  
  // Static method
  static create(name) {
    return new Person(name, 0);
  }
}

const alice = new Person("Alice", 30);
console.log(alice.greet());  // Hello, I'm Alice
console.log(alice.info);     // Alice, 30
console.log(Person.count);   // 1
// alice.#ssn;  // SyntaxError - private!

Наследование и полиморфизм

extends создаёт наследование; super() вызывает родительский конструктор (требуется до использования 'this'). Переопределяйте методы, переопределяя их. JavaScript с одиночным наследованием, но mixins (фабрики классов) обеспечивают композицию. instanceof проверяет цепочку прототипов. Полиморфизм работает через переопределение методов.

javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
  
  // Static method can be inherited
  static create(type, name) {
    return new type(name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // must call super() first
    this.breed = breed;
  }
  
  speak() {
    return `${this.name} barks`;  // override
  }
  
  fetch() {
    return `${this.name} fetches`;
  }
}

class Cat extends Animal {
  speak() {
    return `${this.name} meows`;
  }
}

// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows

// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name);  // "Dog"

// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
  walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}

Прототипы

JavaScript использует прототипное наследование — объекты наследуют от других объектов через цепочку прототипов. __proto__ устарел; используйте Object.getPrototypeOf/setPrototypeOf. Классы — синтаксический сахар над этой системой. Модификация встроенных прототипов (Array.prototype) опасна — может сломать код. Предпочитайте композицию глубокому наследованию.

javascript
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__);  // Object.prototype
console.log(Object.getPrototypeOf(obj));  // preferred

// Constructor function (pre-class syntax)
function OldPerson(name) {
  this.name = name;
}
OldPerson.prototype.greet = function() {
  return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet());  // Hi, Alice

// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null

// Adding to prototype (affects all instances)
Array.prototype.last = function() {
  return this[this.length - 1];
};
console.log([1, 2, 3].last());  // 3

// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type);  // "dog" (own property)

// Check own vs inherited
console.log(dog.hasOwnProperty("type"));  // true
console.log("type" in dog);  // true (includes inherited)

// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
  console.log(proto);
  proto = Object.getPrototypeOf(proto);
}
08

Обработка ошибок

Try / Catch / Finally

try/catch/finally обрабатывает исключения. catch связывает объект ошибки (имеющий .message и .stack). Используйте instanceof для различной обработки конкретных типов ошибок. Всегда перевыбрасывайте неизвестные ошибки после обработки ожидаемых. Создавайте пользовательские ошибки, наследуя Error для специфичной обработки ошибок приложения.

javascript
try {
  const result = JSON.parse('{"invalid json"');
} catch (error) {
  console.error("Parse error:", error.message);
  console.error("Stack:", error.stack);
} finally {
  console.log("Always runs");
}

// Catch specific error types
try {
  const data = JSON.parse(input);
  if (!data.name) throw new TypeError("name is required");
} catch (error) {
  if (error instanceof SyntaxError) {
    console.log("JSON syntax error");
  } else if (error instanceof TypeError) {
    console.log("Type error:", error.message);
  } else {
    throw error;  // re-throw unknown errors
  }
}

// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;

// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalError

Пользовательские ошибки

Наследуйте Error для создания пользовательских типов ошибок с дополнительным контекстом (поля, коды). Всегда устанавливайте this.name равным имени класса. Используйте instanceof для ловли конкретных типов ошибок. Цепочка ошибок (ES2022 { cause }) сохраняет исходную ошибку для отладки. Хорошая иерархия ошибок делает обработку точной.

javascript
// Custom error class
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class DatabaseError extends Error {
  constructor(message, query) {
    super(message);
    this.name = "DatabaseError";
    this.query = query;
  }
}

// Usage
function validateUser(user) {
  if (!user.email) {
    throw new ValidationError("email", "Email is required");
  }
  if (!user.email.includes("@")) {
    throw new ValidationError("email", "Invalid email format");
  }
}

// Handling custom errors
try {
  validateUser({ name: "Alice" });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Validation failed: ${error.field} - ${error.message}`);
  } else if (error instanceof DatabaseError) {
    console.log(`DB error in query: ${error.query}`);
  } else {
    console.log("Unexpected error:", error);
  }
}

// Error chaining (ES2022)
try {
  throw new Error("Original cause");
} catch (cause) {
  throw new Error("Failed to process", { cause });
}
09

Асинхронность и Promise

Колбэки

Колбэки — функции, передаваемые для вызова позже. Конвенция error-first (err, data) — стандарт в Node.js. Вложенные колбэки создают 'callback hell' — глубоко вложенный, трудный для чтения код. Promise и async/await решают это. setTimeout/setInterval — распространённые API на колбэках.

javascript
// Callback pattern (old way)
function fetchData(url, callback) {
  setTimeout(() => {
    callback(null, { data: "result" });
  }, 1000);
}

// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(data);
});

// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
  if (err) return handleError(err);
  fetchPosts(user.id, (err, posts) => {
    if (err) return handleError(err);
    fetchComments(posts[0].id, (err, comments) => {
      if (err) return handleError(err);
      // deeply nested...
    });
  });
});

// Event listeners are callbacks
button.addEventListener("click", (event) => {
  console.log("Clicked!", event.target);
});

// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id);  // stop

Promise

Promise представляют будущие значения с тремя состояниями: pending, fulfilled, rejected. .then() обрабатывает успех, .catch() — ошибки, .finally() выполняется всегда. Promise.all() ждёт все (fail fast); allSettled() ждёт все (никогда не падает); race() возвращает первый завершённый; any() возвращает первый успешный.

javascript
// Creating a promise
const promise = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Data received");
  } else {
    reject(new Error("Failed"));
  }
});

// Consuming a promise
promise
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Done"));

// Chaining
fetch("/api/users")
  .then(response => response.json())
  .then(users => users.filter(u => u.active))
  .then(active => console.log(active))
  .catch(err => console.error("Error:", err));

// Promise.all - wait for all (fails if any fails)
Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
  console.log(users, posts);
});

// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
  results.forEach(r => {
    if (r.status === "fulfilled") console.log(r.value);
    else console.log(r.reason);
  });
});

// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));

// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));

Async / Await

async/await — синтаксический сахар над promise — делает асинхронный код похожим на синхронный. 'await' приостанавливает функцию до завершения promise. Всегда оборачивайте await в try/catch для обработки ошибок. Используйте Promise.all() для параллельных операций (быстрее последовательного await в цикле). Top-level await работает в ES-модулях.

javascript
// async function always returns a Promise
async function fetchData() {
  // await pauses until the promise resolves
  const response = await fetch("/api/users");
  const data = await response.json();
  return data;
}

// Error handling with try/catch
async function getUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }
    return await res.json();
  } catch (error) {
    console.error("Failed:", error);
    return null;
  }
}

// Sequential vs parallel
async function sequential() {
  const a = await fetch("/api/a");  // waits for a
  const b = await fetch("/api/b");  // then waits for b
  return [a, b];
}

async function parallel() {
  const [a, b] = await Promise.all([  // both at once
    fetch("/api/a"),
    fetch("/api/b")
  ]);
  return [a, b];
}

// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());

// Iterating with async
async function processUrls(urls) {
  for (const url of urls) {
    const data = await fetch(url);
    console.log(data);
  }
}

// Promise.all with map (parallel)
async function fetchAll(urls) {
  return Promise.all(urls.map(url => fetch(url)));
}
10

Манипуляция DOM

Выбор и модификация элементов

querySelector/querySelectorAll (CSS-селекторы) — современный способ выбора элементов. textContent безопаснее innerHTML (предотвращает XSS). classList предоставляет add/remove/toggle/contains для классов. dataset обращается к data-* атрибутам. Всегда санитизируйте пользовательский ввод перед установкой innerHTML.

javascript
// Selecting elements
const el = document.querySelector("#app");        // first match
const all = document.querySelectorAll(".item");    // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item");  // HTMLCollection

// Modifying content
el.textContent = "Hello";        // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>";    // HTML (XSS risk with user input!)
el.innerText = "Visible text";   // respects CSS visibility

// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id;  // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");

// Styles
el.style.color = "red";
el.style.backgroundColor = "blue";  // camelCase
el.style.cssText = "color: red; font-size: 16px;";

// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div);   // add to beginning
document.body.insertBefore(div, referenceEl);

События

addEventListener предпочтительнее свойств on<event> (допускает несколько слушателей). Делегирование событий (слушание на родителе) эффективно для динамически добавленных элементов. e.target — что кликнули; e.currentTarget — элемент с слушателем. preventDefault() останавливает поведение по умолчанию; stopPropagation() останавливает всплытие.

javascript
// Add event listener
button.addEventListener("click", (event) => {
  console.log("Clicked!", event.target);
  console.log("Current target:", event.currentTarget);
});

// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll

// Event object properties
input.addEventListener("keydown", (e) => {
  console.log(e.key);       // "Enter", "a", etc.
  console.log(e.code);      // "KeyA", "Enter"
  console.log(e.ctrlKey);   // true if Ctrl held
  e.preventDefault();       // stop default behavior
  e.stopPropagation();      // stop bubbling
});

// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
  if (e.target.matches(".delete-btn")) {
    const id = e.target.dataset.id;
    deleteItem(id);
  }
});

// Custom events
const customEvent = new CustomEvent("userLogin", {
  detail: { userId: 123 }
});
element.dispatchEvent(customEvent);

element.addEventListener("userLogin", (e) => {
  console.log("User logged in:", e.detail.userId);
});

// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
11

Модули и JSON

ES-модули

ES-модули (import/export) — современный стандарт, поддерживается в браузерах и Node.js. Default-экспорт (один на модуль) против именованных экспортов (несколько). Динамический import() включает ленивую загрузку. Модули всегда в строгом режиме и имеют собственную область видимости. Используйте type='module' в HTML script-тегах.

javascript
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }

// Default export (one per module)
export default function greet(name) {
  return `Hello, ${name}`;
}

// main.js - importing
import greet from "./math.js";              // default
import { add, PI } from "./math.js";        // named
import * as math from "./math.js";          // namespace
import { add as plus } from "./math.js";    // rename
import greet, { add } from "./math.js";     // default + named

// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));

// Conditional/dynamic loading
if (featureEnabled) {
  const { default: Feature } = await import("./feature.js");
  new Feature();
}

// Re-export
export { add } from "./math.js";
export * from "./utils.js";

// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by default

JSON

JSON.stringify() преобразует JS-значения в JSON-строки; JSON.parse() обращает. Используйте replacers/revivers для фильтрации или преобразования во время конвертации. JSON не поддерживает функции, undefined, Date (становятся строками) или циклические ссылки. fetch().json() автоматически парсит JSON-ответы. Всегда оборачивайте JSON.parse в try/catch для недоверенного ввода.

javascript
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object

// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'

// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
//   "name": "Alice",
//   "age": 30,
//   "active": true
// }

// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name);  // "Bob"

// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
  if (key === "age") return undefined;  // exclude
  return value;
});

// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
  if (key === "date") return new Date(value);
  return value;
});

// Fetch JSON from API
async function getUsers() {
  const res = await fetch("/api/users");
  return res.json();  // parses JSON automatically
}

// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)

Дата и время

JavaScript Date печально неудобен — месяцы 0-индексированы (январь = 0), дни 1-индексированы. Объекты Date изменяемы. toISOString() даёт UTC; toLocaleString() — локальное время. Для серьёзной работы с датами используйте библиотеку вроде date-fns или dayjs. Intl.DateTimeFormat предоставляет локализованное форматирование.

javascript
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30);  // month is 0-indexed!

// Get components
console.log(now.getFullYear());   // 2024
console.log(now.getMonth());      // 0-11 (January = 0!)
console.log(now.getDate());       // 1-31
console.log(now.getDay());        // 0-6 (Sunday = 0)
console.log(now.getHours());      // 0-23
console.log(now.getTime());       // milliseconds since epoch

// Set components
now.setFullYear(2025);
now.setMonth(11);  // December

// Formatting
console.log(now.toISOString());     // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString());  // "1/15/2024, 10:30:00 AM"

// Timestamps
const start = Date.now();  // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);

// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
  year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now));  // "2024年1月15日"
12

Возможности ES6+

let, const и блочная область видимости

Предпочитайте const по умолчанию, let при необходимости переназначения, избегайте var полностью. const предотвращает переназначение, но объекты/массивы всё ещё изменяемы. let и const имеют блочную область видимости и живут во Temporal Dead Zone до объявления (в отличие от var, который поднимается как undefined). Это предотвращает множество тонких багов.

javascript
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
  var x = 1;   // accessible outside the block
  let y = 2;   // block-scoped
  const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError

// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4);      // OK — mutating the array
// arr = [5];     // TypeError — reassigning const

// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;

Стрелочные функции и this

Стрелочные функции лаконичны и наследуют 'this' из объемлющей области видимости — идеально для callback'ов и методов, нуждающихся во внешнем 'this'. Но их нельзя использовать как конструкторы, и нет объекта 'arguments'. Не используйте стрелочные функции для методов объектов, если нужно, чтобы 'this' ссылался на объект (используйте обычные методы).

javascript
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x;          // single param, no parens
const greet = name => `Hello, ${name}`;  // template literal
const noop = () => {};              // no params, empty body

// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });

// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
  this.count = 0;
  setInterval(() => {
    this.count++;  // 'this' is the Counter instance (lexical)
    console.log(this.count);
  }, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)

Деструктурирующее присваивание

Деструктуризация извлекает значения из объектов/массивов в переменные в одну строку — чище ручного доступа к свойствам. Деструктуризация объектов использует { key }, массивов — [index]. Поддерживает переименование (key: alias), значения по умолчанию (key = default), rest (...rest) и вложенные паттерны. Активно используется в React props и параметрах функций.

javascript
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age);  // Alice 30

// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country);  // Alice USA

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest);  // 1 2 [3,4,5]

// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);  // 2 1

// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
  console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" });  // Hello, Bob

// Nested destructuring
const { data: { results } } = response;

Операторы spread и rest

Оператор ... — 'spread' при разворачивании (в массивах/объектах/вызовах) и 'rest' при сборе (в параметрах/деструктуризации). Spread создаёт поверхностные копии и сливает объекты (поздние ключи переопределяют ранние). Rest-параметры заменяют старый объект 'arguments' и являются настоящими Array. Оба — необходимые современные JS-идиомы.

javascript
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 };  // override b: { a: 1, b: 99 }

// Rest (...) collects multiple elements into an array
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10

// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail);  // 1 [2, 3, 4]

// Spread in function calls
Math.max(...[1, 5, 3]);  // 5 (same as Math.max(1, 5, 3))

Шаблонные литералы и тегированные шаблоны

Шаблонные литералы (обратные кавычки) поддерживают многострочные строки и интерполяцию ${} — гораздо чище конкатенации строк. Тегированные шаблоны позволяют функции обрабатывать литеральные части и интерполированные значения, обеспечивая пользовательское форматирование, санитизацию (например, экранирование HTML) или i18n. Популярны в styled-components и graphql-tag.

javascript
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
  this spans
  multiple lines`;

// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`);  // Total: 21.59

// Nested template literals
const items = ["apple", "banana"];
const html = `
  <ul>
    ${items.map(i => `<li>${i}</li>`).join("")}
  </ul>
`;

// Tagged templates: function processes the literal
function highlight(strings, ...values) {
  return strings.reduce((acc, str, i) =>
    acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"

Optional Chaining и Nullish Coalescing

Optional chaining (?.) коротко замыкается на undefined, если любая часть цепочки null/undefined — устраняет многословные ручные проверки. Nullish coalescing (??) предоставляет значения по умолчанию ТОЛЬКО для null/undefined, в отличие от ||, который также переопределяет 0, '' и false. Вместе они безопасно обрабатывают самые распространённые паттерны 'отсутствующих данных'. Доступно с ES2020.

javascript
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name);     // "Alice"
console.log(user?.settings?.theme);   // undefined (no error!)
console.log(user?.profile?.address?.city);  // undefined

// Without ?., you'd write:
// user && user.profile && user.profile.name

// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];

// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light";  // "light"
const count = 0 ?? 10;  // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous";  // "" (empty string is kept!)

// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10;  // 10 (probably not what you want!)
13

События и обработка событий

Основы addEventListener

addEventListener — современный способ привязки событий — допускает несколько обработчиков, поддерживает делегирование событий и предлагает опции once, passive и capture. Всегда храните ссылку на обработчик, если нужно удалить его позже (анонимные функции нельзя удалить). Объект события содержит target, currentTarget, preventDefault() и stopPropagation().

javascript
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");

button.addEventListener("click", function(event) {
  console.log("Clicked!", event.target);
  event.preventDefault();  // stop default action (e.g., form submit)
});

// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);

// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);

// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"

// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });

Делегирование событий

Делегирование событий привязывает один слушатель к родителю, обрабатывающий события от всех дочерних элементов через всплытие. Используйте event.target.matches(selector) для фильтрации. Это гораздо эффективнее привязки к каждому дочернему элементу и автоматически обрабатывает динамически добавленные элементы. Компромисс: родитель должен быть общим предком, который всегда существует.

javascript
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");

list.addEventListener("click", (event) => {
  // event.target is the actual element clicked
  if (event.target.matches("li.item")) {
    console.log("Clicked:", event.target.textContent);
    event.target.classList.toggle("selected");
  }
});

// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");

// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changes

Распространение событий (всплытие и перехват)

События распространяются в три фазы: capturing (сверху вниз), target и bubbling (снизу вверх, по умолчанию). Большинство обработчиков выполняются в фазе всплытия. stopPropagation() предотвращает достижение события родительских элементов; stopImmediatePropagation() также останавливает другие обработчики на том же элементе. Используйте capturing (третий аргумент true) для обработчиков, которые должны выполниться до дочерних.

javascript
<div id="outer">
  <div id="inner">
    <button id="btn">Click</button>
  </div>
</div>

// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)

// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer

// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
  e.stopPropagation();
  console.log("only button");
});

// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true);  // runs during capture

// stopImmediatePropagation: stop other handlers on SAME element too

Пользовательские события

CustomEvent позволяет создавать специфичные для приложения события с данными в свойстве 'detail'. В сочетании с dispatchEvent это включает паттерн pub/sub для развязки компонентов — модули общаются без прямых ссылок. Используйте соглашение об именовании вроде 'namespace:action' для избежания коллизий. Это основа многих фреймворков пользовательских элементов.

javascript
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
  detail: { userId: 42, name: "Alice" },
  bubbles: true,    // allow bubbling
});

document.dispatchEvent(event);

// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
  console.log("User logged in:", e.detail.userId, e.detail.name);
});

// Practical: decoupled communication between components
class Cart {
  constructor() {
    this.items = [];
  }
  add(item) {
    this.items.push(item);
    window.dispatchEvent(new CustomEvent("cart:updated", {
      detail: { count: this.items.length }
    }));
  }
}

// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
  updateBadge(e.detail.count);
});

События клавиатуры и форм

События клавиатуры дают e.key (логическая клавиша вроде 'a', 'Enter') и e.code (физическая клавиша вроде 'KeyA'). Используйте e.key для большинства случаев. Отправка формы всегда требует preventDefault() для остановки перезагрузки страницы. FormData + Object.fromEntries легко собирает данные формы. 'input' срабатывает непрерывно; 'change' — при потере фокуса — выбирайте в зависимости от момента валидации.

javascript
// Keyboard events
document.addEventListener("keydown", (e) => {
  console.log(e.key, e.code);  // e.key="a", e.code="KeyA"
  if (e.key === "Escape") closeModal();
  if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});

// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
  e.preventDefault();  // stop page reload
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  console.log(data);  // { username: "...", email: "..." }
});

// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
  if (!emailInput.value.includes("@")) {
    emailInput.setCustomValidity("Enter a valid email");
  } else {
    emailInput.setCustomValidity("");
  }
});

// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blur
14

Fetch API и AJAX

Базовый fetch (GET)

fetch() — современная замена XMLHttpRequest — на основе promise и чище. Важно: fetch отклоняет только при сетевых ошибках, НЕ при HTTP-ошибках (404, 500). Всегда проверяйте response.ok (статус 200-299) перед парсингом. Используйте async/await для читаемого последовательного кода. response.json() асинхронный, потому что читает поток тела.

javascript
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json();  // or .text(), .blob(), .arrayBuffer()
  })
  .then(data => console.log(data))
  .catch(error => console.error("Fetch failed:", error));

// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.

// Same with async/await (preferred)
async function getUsers() {
  try {
    const res = await fetch("https://api.example.com/users");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error(err);
  }
}

POST, PUT, DELETE с fetch

Объект options в fetch настраивает method, headers и body. Для JSON установите Content-Type: application/json и JSON.stringify body. Для загрузки файлов используйте FormData (не устанавливайте Content-Type вручную — браузер добавляет multipart boundary). PUT заменяет ресурс целиком; PATCH частично обновляет.

javascript
// POST: create a resource
async function createUser(data) {
  const res = await fetch("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });

// PUT: update a resource (full replace)
await fetch("/api/users/1", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice Updated" }),
});

// DELETE
await fetch("/api/users/1", { method: "DELETE" });

// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundary

Заголовки запросов и аутентификация

Заголовки переносят метаданные и auth-токены. Bearer-токены (JWT) идут в заголовке Authorization. Некоторые заголовки 'запрещённые' (контролируемые браузером) вроде Host и Cookie. CORS обеспечивается браузером, а не сервером — нельзя обойти из клиентского JS; сервер должен отправить Access-Control-Allow-Origin. Preflight OPTIONS-запросы происходят для non-simple запросов.

javascript
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
  const token = localStorage.getItem("authToken");
  const res = await fetch(url, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
      "Accept": "application/json",
      ...options.headers,  // allow overrides
    },
  });
  return res;
}

// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)

// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.

AbortController (отмена запросов)

AbortController отменяет fetch-запросы — необходимо для search-as-you-type, ухода со страницы или таймаутов. Передайте signal в fetch; вызов controller.abort() триггерит AbortError. Без этого устаревшие запросы могут обновить UI не по порядку. AbortController также работает с другими async-API и является стандартным механизмом отмены в современном JS.

javascript
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();

async function fetchWithTimeout(url, ms = 5000) {
  const timeout = setTimeout(() => controller.abort(), ms);
  try {
    const res = await fetch(url, { signal: controller.signal });
    return await res.json();
  } catch (err) {
    if (err.name === "AbortError") {
      console.log("Request was aborted (timeout or cancel)");
    } else {
      throw err;
    }
  } finally {
    clearTimeout(timeout);
  }
}

// Cancel manually (e.g., user navigated away)
// controller.abort();

// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
  controller.abort();  // cancel previous request
  fetchResults(e.target.value);
});

Потоковые ответы

response.body — ReadableStream — можно обрабатывать данные чанками по мере поступления вместо буферизации всего ответа в памяти. Это необходимо для больших файлов, потоковых логов или данных в реальном времени. Используйте TextDecoder для текстовых потоков. Цикл reader.read() продолжается, пока done не станет true. Стриминг избегает скачков памяти на больших payload.

javascript
// Read a large response in chunks (streaming)
async function streamJson(url) {
  const res = await fetch(url);
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    // Process complete lines
    const lines = buffer.split("\n");
    buffer = lines.pop();  // keep incomplete line
    for (const line of lines) {
      if (line.trim()) console.log(JSON.parse(line));
    }
  }
}

// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full response
15

Веб-хранилище (LocalStorage и SessionStorage)

Основы localStorage и sessionStorage

localStorage хранит бесконечно; sessionStorage очищается при закрытии вкладки. Оба хранят только строки — используйте JSON.stringify/parse для объектов. Хранилище синхронное и блокирует основной поток, поэтому избегайте хранения больших данных. Доступно во всех современных браузерах, но может быть отключено в режиме приватного просмотра. Ёмкость ~5-10МБ на origin.

javascript
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes

// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username");  // "Alice"
localStorage.removeItem("username");
localStorage.clear();  // remove ALL items

// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");

// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme);  // "dark"

// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
  // localStorage/sessionStorage available
}

Помощник хранилища с TTL и JSON

Веб-хранилище не имеет встроенного истечения — эта обёртка добавляет TTL (time-to-live), сохраняя отметку истечения вместе со значением. Это стандартный паттерн для кэширования API-ответов или данных сессии, которые должны истекать. Всегда оборачивайте доступ к хранилищу в try/catch в production, так как JSON.parse может выбросить исключение на повреждённых данных, а квота может быть превышена.

javascript
// A robust storage wrapper with expiration (TTL)
const store = {
  set(key, value, ttlMs) {
    const item = {
      value: value,
      expiry: ttlMs ? Date.now() + ttlMs : null,
    };
    localStorage.setItem(key, JSON.stringify(item));
  },
  get(key) {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const item = JSON.parse(raw);
    if (item.expiry && Date.now() > item.expiry) {
      localStorage.removeItem(key);
      return null;  // expired
    }
    return item.value;
  },
  remove(key) { localStorage.removeItem(key); },
};

// Usage: cache data for 5 minutes
store.set("userData", { name: "Alice" }, 5 * 60 * 1000);
console.log(store.get("userData"));  // { name: "Alice" }
// After 5 min: returns null and cleans up

События хранилища (синхронизация между вкладками)

Событие storage — встроенный канал связи между вкладками — когда одна вкладка модифицирует localStorage, все другие вкладки того же origin получают событие (но не исходная вкладка). Это позволяет синхронизировать состояние вроде login/logout, обновлений корзины или смены темы между вкладками без WebSockets. Событие включает key, oldValue, newValue и url.

javascript
// The 'storage' event fires in OTHER tabs when storage changes
// (not in the tab that made the change)
window.addEventListener("storage", (event) => {
  console.log("Key changed:", event.key);
  console.log("Old value:", event.oldValue);
  console.log("New value:", event.newValue);
  console.log("URL:", event.url);

  if (event.key === "cart") {
    updateCartDisplay(JSON.parse(event.newValue));
  }
});

// Practical: sync logout across tabs
// Tab A: localStorage.setItem("logout", Date.now());
// Tab B: receives storage event -> redirects to login

// Practical: broadcast a message to all tabs
function broadcast(type, data) {
  localStorage.setItem("broadcast", JSON.stringify({ type, data, t: Date.now() }));
}
window.addEventListener("storage", (e) => {
  if (e.key === "broadcast") {
    const msg = JSON.parse(e.newValue);
    handleMessage(msg);
  }
});

IndexedDB (крупное структурированное хранилище)

IndexedDB — мощная NoSQL база данных в браузере — асинхронная, транзакционная и способная хранить гораздо больше данных, чем localStorage (сотни МБ). Поддерживает индексы, курсоры и транзакции. Сырой API на основе колбэков и многословен; npm-пакет 'idb' предоставляет чистую обёртку на основе Promise. Используйте IndexedDB для offline-first приложений, больших кэшей или сложных клиентских данных.

javascript
// IndexedDB: async, transactional NoSQL store for large data
// Capacity: hundreds of MB to GB (far more than localStorage's 5MB)

// Open a database
const request = indexedDB.open("MyDatabase", 1);

request.onupgradeneeded = (e) => {
  const db = e.target.result;
  // Create an object store (like a table) with a key path
  if (!db.objectStoreNames.contains("users")) {
    db.createObjectStore("users", { keyPath: "id" });
  }
};

request.onsuccess = (e) => {
  const db = e.target.result;
  // Add data in a transaction
  const tx = db.transaction("users", "readwrite");
  const store = tx.objectStore("users");
  store.add({ id: 1, name: "Alice", age: 30 });
  store.add({ id: 2, name: "Bob", age: 25 });

  // Query
  store.get(1).onsuccess = (e) => {
    console.log(e.target.result);  // { id: 1, name: "Alice", age: 30 }
  };
};

// For easier use, consider the 'idb' library (Promise-based wrapper)

Сравнение Cookies и хранилища

Cookies отправляются с каждым HTTP-запросом (увеличивая трафик) и являются стандартом для серверной аутентификации (session ID, CSRF-токены). localStorage/sessionStorage — только клиентские и хранят гораздо больше данных. IndexedDB — для крупных структурированных данных. Выбирайте: нужно ли серверу? (cookie) Сколько данных? (storage vs IndexedDB) Как долго? (session vs local). Устанавливайте Secure, HttpOnly и SameSite на чувствительные к безопасности cookies.

javascript
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie);  // "session=abc123; theme=dark"

// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)

// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");

// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");

// WHEN TO USE WHAT:
// Cookies   -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queries
16

Таймеры (setTimeout, setInterval)

setTimeout и setInterval

setTimeout запускает колбэк один раз после задержки; setInterval запускает его повторно. Оба возвращают ID для отмены через clearTimeout/clearInterval. Таймеры не точные — это минимальные задержки, зависящие от цикла событий, видимости вкладки (throttle в фоновых вкладках) и доступности основного потока. Задержки менее 4мс могут быть ограничены до 4мс во вложенных таймерах.

javascript
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
  console.log("Runs after 2 seconds");
}, 2000);

// Cancel before it fires
clearTimeout(timeoutId);

// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
  console.log("Runs every 1 second");
}, 1000);

// Stop the interval
clearInterval(intervalId);

// Pass arguments to the callback
setTimeout((greeting, name) => {
  console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");

// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)

Рекурсивный setTimeout (лучше setInterval)

Рекурсивный setTimeout предпочтительнее setInterval для повторяющихся async-задач, потому что гарантирует завершение предыдущего вызова до начала следующего — без перекрывающихся выполнений. Также допускает динамические интервалы (например, больший backoff при ошибках). setInterval может накапливать вызовы, если обработчик занимает больше времени, чем интервал, вызывая проблемы с производительностью.

javascript
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.

// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
  fetch("/api/status")
    .then(r => r.json())
    .then(data => {
      console.log(data);
      setTimeout(poll, 1000);  // schedule next AFTER this finishes
    })
    .catch(err => {
      console.error(err);
      setTimeout(poll, 5000);  // retry with backoff on error
    });
}
poll();  // start the loop

// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.

requestAnimationFrame (плавные анимации)

requestAnimationFrame (rAF) — правильный способ визуальных анимаций — он синхронизируется с циклом перерисовки браузера (~60fps), избегает ненужных кадров при скрытой вкладке и производит более плавные анимации, чем setInterval. Используйте параметр timestamp для расчётов delta-time для сохранения скорости анимации на разных частотах обновления. Всегда отменяйте через cancelAnimationFrame по завершении.

javascript
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
  element.style.left = parseInt(element.style.left || 0) + 2 + "px";
  if (parseInt(element.style.left) < 300) {
    requestAnimationFrame(animate);  // schedule next frame
  }
}
requestAnimationFrame(animate);

// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);

// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
  const delta = timestamp - lastTime;
  lastTime = timestamp;
  update(delta);  // frame-rate independent movement
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

// rAF pauses in background tabs (saves CPU/battery)

Debounce и Throttle

Debounce ждёт паузы в вызовах перед выполнением (например, поиск только через 300мс после остановки ввода). Throttle ограничивает выполнение одним разом за интервал (например, обновление позиции скролла максимум каждые 200мс). Оба предотвращают проблемы с производительностью от высокочастотных событий. Debounce = 'группировать быстрые вызовы в один'; throttle = 'ограничить частоту вызовов'.

javascript
// Debounce: delay execution until calls stop for N ms
// Use for: search input, window resize, button spam
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);  // reset the countdown
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const search = debounce((query) => {
  fetchResults(query);
}, 300);
input.addEventListener("input", (e) => search(e.target.value));

// Throttle: execute at most once per N ms
// Use for: scroll, mousemove, drag handlers
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

const onScroll = throttle(() => {
  console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", onScroll);

Промисифицированный setTimeout (delay)

Обёртывание setTimeout в Promise создаёт чистую функцию 'delay' для async/await — гораздо читабельнее цепочек колбэков. Этот паттерн питает логику повторов с экспоненциальным backoff (ожидание 1с, 2с, 4с между попытками), последовательные анимации и rate limiting. Помощник delay — одна из самых полезных маленьких утилит в современном async JS.

javascript
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function countdown() {
  console.log("3...");
  await delay(1000);
  console.log("2...");
  await delay(1000);
  console.log("1...");
  await delay(1000);
  console.log("Go!");
}
countdown();

// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url);
      if (res.ok) return await res.json();
      throw new Error(`HTTP ${res.status}`);
    } catch (err) {
      if (i === retries - 1) throw err;
      const wait = Math.pow(2, i) * 1000;  // 1s, 2s, 4s
      console.log(`Retry ${i + 1} in ${wait}ms`);
      await delay(wait);
    }
  }
}
17

Map, Set и WeakMap

Map (ключ-значение с любым типом ключа)

Map — полноценная коллекция ключ-значение, где ключи могут быть любого типа (объекты, функции, числа) — в отличие от Objects, которые приводят ключи к строкам. Map сохраняет порядок вставки, имеет свойство .size и напрямую итерируем. Используйте Map, когда нужны нестроковые ключи, частые добавления/удаления или когда коллекция не является record/DTO. Используйте Object для данных фиксированной формы.

javascript
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();

// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");

console.log(map.get("name"));   // "Alice"
console.log(map.get(objKey));   // "data for this object"
console.log(map.size);          // 4
console.log(map.has(42));       // true

// Iteration preserves insertion order
for (const [key, value] of map) {
  console.log(key, value);
}

// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/remove

Set (уникальные значения)

Set хранит уникальные значения — идеально для дедупликации и проверки принадлежности. Преобразование массива в Set и обратно ([...new Set(arr)]) — идиоматичный способ удаления дубликатов. Set.has() — O(1) против Array.includes() — O(n), поэтому используйте Set для больших коллекций, которые часто проверяете. Set не имеет map/filter — разворачивайте в массив сначала.

javascript
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set);        // Set(3) { 1, 2, 3 }
console.log(set.size);   // 3

set.add(4);
set.add(4);  // ignored (already exists)
console.log(set.has(3));  // true
set.delete(2);

// Iterate (preserves insertion order)
for (const item of set) {
  console.log(item);
}

// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])];  // [1, 2, 3]

// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }

// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]);        // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x)));  // {2,3}

WeakMap и WeakSet (memory-safe ссылки)

WeakMap и WeakSet держат слабые ссылки на объекты — когда других ссылок нет, запись собирается сборщиком мусора автоматически. Это предотвращает утечки памяти при ассоциации данных с DOM-элементами или другими объектами. Ключи должны быть объектами. WeakMap/WeakSet не итерируемы и не имеют .size, потому что записи могут исчезнуть в любое время во время GC. Используйте для кэширования/метаданных, привязанных к времени жизни объекта.

javascript
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();

let user = { name: "Alice" };
weakMap.set(user, "metadata");

console.log(weakMap.get(user));  // "metadata"

user = null;  // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically

// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
  elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned

// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
  if (processed.has(obj)) return;  // already done
  processed.add(obj);
  doWork(obj);
}

// WeakMap/WeakSet are NOT iterable (no .size, no for...of)

Итерация и преобразование Map

Map итерируемы в порядке вставки через for...of (entries по умолчанию), .keys(), .values() или .forEach(). Преобразуйте между Map и Object с помощью Object.entries() и Object.fromEntries(). Это двустороннее преобразование удобно, когда API ожидают обычные объекты, но вы хотите возможности Map внутри. Помните: ключи Object становятся строковым типом при преобразовании.

javascript
const map = new Map([
  ["name", "Alice"],
  ["age", 30],
  ["city", "NYC"],
]);

// Iterate entries (default)
for (const [key, value] of map) {
  console.log(`${key} = ${value}`);
}

// Iterate keys only
for (const key of map.keys()) {
  console.log(key);
}

// Iterate values only
for (const value of map.values()) {
  console.log(value);
}

// forEach (callback style)
map.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

// Convert to Array
const entries = [...map.entries()];  // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()];        // ["name", "age", "city"]
const values = [...map.values()];    // ["Alice", 30, "NYC"]

// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);

Выбор между Map, Set, Object и Array

Выбирайте правильную коллекцию: Arrays для упорядоченных списков с дубликатами и доступом по индексу; Objects для записей фиксированной формы и JSON; Maps для динамических коллекций ключ-значение с любым типом ключа; Sets для уникальности и быстрого поиска. Использование неправильной структуры приводит к многословному коду и проблемам производительности — например, Array.includes() в цикле против Set.has().

javascript
// Decision guide:

// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];

// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };

// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());

// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }
18

Генераторы и итераторы

Итераторы и Symbol.iterator

Итератор имеет метод next(), возвращающий {value, done}. Итерируемый объект реализует Symbol.iterator, который возвращает итератор. Встроенные итерируемые (массивы, строки, Map, Set) работают с for...of, spread (...), деструктуризацией и Array.from(). Реализация Symbol.iterator позволяет вашим пользовательским объектам работать со всеми этими языковыми возможностями бесшовно.

javascript
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
  let i = 0;
  return {
    next() {
      return i < arr.length
        ? { value: arr[i++], done: false }
        : { value: undefined, done: true };
    },
  };
}

const it = makeIterator(["a", "b", "c"]);
console.log(it.next());  // { value: "a", done: false }
console.log(it.next());  // { value: "b", done: false }
console.log(it.next());  // { value: "c", done: false }
console.log(it.next());  // { value: undefined, done: true }

// An iterable implements Symbol.iterator (returns an iterator)
const range = {
  from: 1, to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      },
    };
  },
};

// Now range works with for...of, spread, etc.
console.log([...range]);  // [1, 2, 3]

Функции-генераторы (function*)

Функции-генераторы (function*) используют yield для приостановки и возобновления выполнения — они производят значения лениво, по одному за раз. Это включает бесконечные последовательности, ленивые вычисления и эффективные по памяти конвейеры. Генераторы являются и итераторами, и итерируемыми. Каждый вызов next() выполняется до следующего yield (или return). Они — основа async-генераторов и JS-паттернов корутин.

javascript
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;  // pause here, return value, resume on next()
  }
}

const gen = idGenerator();
console.log(gen.next());  // { value: 1, done: false }
console.log(gen.next());  // { value: 2, done: false }
console.log(gen.next());  // { value: 3, done: false }
// Infinite sequence — only computes values on demand

// Generators are iterables (work with for...of)
function* take(n, iterable) {
  for (const item of iterable) {
    if (n-- <= 0) return;
    yield item;
  }
}

function* naturalNumbers() {
  let n = 1;
  while (true) yield n++;
}

const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive);  // [1, 2, 3, 4, 5]

yield* (делегирование другому генератору)

yield* делегирует все yield другому итерируемому объекту или генератору — выравнивая вложенные структуры и чисто составляя генераторы. Это JS-эквивалент Python 'yield from'. Значения делегированного генератора выводятся по одному, как если бы они были частью внешнего генератора. Это стандартный способ составления генераторов рекурсивно.

javascript
// yield* delegates to another iterable/generator
function* inner() {
  yield "a";
  yield "b";
}

function* outer() {
  yield 1;
  yield* inner();     // delegates: yields "a", then "b"
  yield* [10, 20];    // also works with arrays
  yield 2;
}

console.log([...outer()]);  // [1, "a", "b", 10, 20, 2]

// Practical: flatten nested structures
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flatten(item);  // recursive delegation
    } else {
      yield item;
    }
  }
}

const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]);  // [1, 2, 3, 4, 5]

send() генератора через next(value)

Генераторы поддерживают двустороннюю связь: next(value) передаёт значение внутрь (становится результатом последнего yield-выражения), а throw(error) инъектирует исключение в точке yield. Это включает корутины, конечные автоматы и генераторные async-паттерны, которые питали ранние реализации async/await. Первый вызов next() не может передать значение (нечего его принимать).

javascript
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
  const name = yield "What's your name?";  // receives via next()
  const age = yield `Hello ${name}, how old are you?`;
  return `${name} is ${age} years old`;
}

const talk = conversation();
console.log(talk.next().value);          // "What's your name?"
console.log(talk.next("Alice").value);   // "Hello Alice, how old are you?"
console.log(talk.next(30).value);        // "Alice is 30 years old"

// throw(error) injects an exception at the yield point
function* safeGen() {
  try {
    yield "step 1";
  } catch (e) {
    console.log("Caught:", e.message);
    yield "recovered";
  }
}
const g = safeGen();
g.next();           // "step 1"
g.throw(new Error("oops"));  // "Caught: oops" -> "recovered"

Асинхронные генераторы

Асинхронные генераторы (async function*) объединяют генераторы с async/await — каждый yield может произвести значение после await. Потребляйте через for await...of. Идеально для пагинированных API, потоковых данных или любого сценария, где значения производятся асинхронно. Это современный способ обработки потоковых данных в JS без колбэков или ручного chaining promise.

javascript
// async function* : yields promises, can use await
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.items.length === 0) break;  // no more pages
    yield data.items;
    page++;
  }
}

// Consume with for await...of
(async () => {
  for await (const items of fetchPages("/api/products")) {
    console.log(`Got ${items.length} items`);
    renderItems(items);
  }
  console.log("All pages loaded");
})();

// Practical: stream data as it arrives
async function* streamLines(response) {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop();
    for (const line of lines) yield line;
  }
}
19

Proxy и Reflect

Основы Proxy (перехват операций)

Proxy позволяет перехватывать и настраивать фундаментальные операции над объектом — get, set, has, deleteProperty, ownKeys и более. Объект handler определяет 'ловушки' (как геттеры/сеттеры, но для всех свойств). Сценарии использования: валидация, логирование, значения по умолчанию, контроль доступа, реактивные системы (Vue 3 использует Proxy для реактивности). Возвращайте true из set для индикации успеха в строгом режиме.

javascript
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };

const handler = {
  get(obj, prop) {
    console.log(`Reading ${prop}`);
    return prop in obj ? obj[prop] : "N/A";  // default value
  },
  set(obj, prop, value) {
    console.log(`Setting ${prop} = ${value}`);
    if (prop === "age" && value < 0) return false;  // validation
    obj[prop] = value;
    return true;  // indicate success
  },
};

const proxy = new Proxy(target, handler);
console.log(proxy.name);   // logs "Reading name", returns "Alice"
proxy.age = 25;            // logs "Setting age = 25"
// proxy.age = -5;         // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"

Распространённые сценарии использования Proxy

Proxy включают мощное метапрограммирование: отрицательные индексы, ленивые вычисления свойств, read-only представления, валидация, логирование и реактивное связывание данных. Система реактивности Vue 3 использует Proxy для отслеживания доступа к свойствам и автоматического запуска re-render. Компромисс — небольшой overhead производительности, поэтому используйте Proxy там, где абстракция оправдана, а не для каждого объекта.

javascript
// 1. Negative array indices (like Python)
function negativeArray(arr) {
  return new Proxy(arr, {
    get(target, prop) {
      const idx = Number(prop);
      if (Number.isInteger(idx)) {
        return target[idx < 0 ? target.length + idx : idx];
      }
      return target[prop];
    },
  });
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]);  // "c"

// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
  get(target, prop) {
    if (!(prop in target)) {
      target[prop] = expensiveCompute(prop);  // compute on first access
    }
    return target[prop];
  },
});

// 3. Read-only object
const readOnly = new Proxy(data, {
  set() { throw new Error("This object is read-only"); },
  deleteProperty() { throw new Error("Cannot delete"); },
});

Reflect API

Reflect предоставляет те же операции, которые перехватывают ловушки Proxy — используйте его внутри ловушек для чистого перенаправления к поведению по умолчанию. Методы Reflect возвращают булевы значения (успех/неудача) вместо выброса исключений, что безопаснее для условной логики. Reflect.ownKeys возвращает все ключи (строки AND символы), в отличие от Object.keys, который возвращает только перечислимые строковые ключи. Вместе Proxy и Reflect формируют набор инструментов метапрограммирования JS.

javascript
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations

const obj = { x: 1, y: 2 };

// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x"));    // 1
Reflect.set(obj, "z", 3);              // obj.z = 3
console.log(Reflect.has(obj, "x"));    // true (like "x" in obj)
Reflect.deleteProperty(obj, "y");      // delete obj.y
console.log(Reflect.ownKeys(obj));     // ["x", "z"]

// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
  get(target, prop, receiver) {
    console.log(`get ${prop}`);
    return Reflect.get(target, prop, receiver);  // default behavior
  },
  set(target, prop, value, receiver) {
    console.log(`set ${prop}`);
    return Reflect.set(target, prop, value, receiver);
  },
});

// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);

Реактивный объект с Proxy (Vue-стиль)

Это основная идея реактивности Vue 3 — Proxy перехватывает get (для отслеживания, какие эффекты зависят от свойства) и set (для запуска этих эффектов при изменении свойства). Это включает декларативные обновления UI: вы мутируете состояние, а фреймворк автоматически re-render. Proxy сделали это возможным в ES6; ранние фреймворки (Vue 2) использовали Object.defineProperty с ограничениями.

javascript
// A minimal reactive system using Proxy
function reactive(target) {
  const subscribers = new Set();

  const handler = {
    get(obj, prop) {
      track(prop);  // record who reads this property
      const value = Reflect.get(obj, prop);
      return typeof value === "object" && value !== null
        ? reactive(value)  // deeply reactive
        : value;
    },
    set(obj, prop, value) {
      const result = Reflect.set(obj, prop, value);
      trigger(prop);  // notify subscribers
      return result;
    },
  };

  let currentEffect = null;
  function track(prop) {
    if (currentEffect) subscribers.add(currentEffect);
  }
  function trigger(prop) {
    subscribers.forEach(fn => fn());
  }

  return new Proxy(target, handler);
}

const state = reactive({ count: 0 });
// When state.count changes, effects re-run automatically
20

Web Workers

Создание Web Worker

Web Workers запускают JavaScript в отдельном фоновом потоке, обеспечивая истинный параллелизм без блокировки UI. Основной поток и worker общаются через postMessage (данные копируются/structured-cloned, не разделяются). Workers не могут обращаться к DOM или объекту window — они изолированы. Используйте workers для CPU-интенсивных задач вроде обработки изображений, парсинга больших файлов или сложных вычислений.

javascript
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");

// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });

// Receive results from the worker
worker.onmessage = (event) => {
  console.log("Result from worker:", event.data);
};

worker.onerror = (error) => {
  console.error("Worker error:", error.message);
};

// Terminate when done (frees resources)
// worker.terminate();

// --- worker.js (separate file) ---
// self.onmessage = (event) => {
//   const { command, data } = event.data;
//   const result = heavyComputation(data);
//   self.postMessage(result);
// };

Inline Workers (Blob URL)

Inline workers создают Worker из строки кода через Blob URL — без отдельного файла. Удобно для демо, маленьких утилит или когда ваша система сборки не легко обрабатывает отдельные worker-файлы. Не забудьте отозвать object URL для избежания утечек памяти. Код worker — строка, поэтому вы теряете подсветку синтаксиса в редакторе и проверку типов — используйте с осторожностью в production.

javascript
// Create a worker from a string (no separate file needed)
const workerCode = `
  self.onmessage = function(e) {
    const result = e.data.map(x => x * x);
    self.postMessage(result);
  };
`;

const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

worker.postMessage([1, 2, 3, 4]);
worker.onmessage = (e) => console.log(e.data);  // [1, 4, 9, 16]

// Clean up the blob URL when done
// URL.revokeObjectURL(blob URL);

// Useful for: single-file demos, bundlers that inline workers,
// or when you can't serve a separate .js file

Transferable Objects (zero-copy)

Обычно postMessage копирует данные (structured clone), что медленно для больших буферов. Transfer list (второй аргумент) ПЕРЕДАЁТ владение ArrayBuffer/MessagePort/ImageBitmap в worker с нулевым копированием — почти мгновенно независимо от размера. Исходный буфер становится detached (непригодным). Используйте для больших наборов данных, обработки изображений или аудио, чтобы избежать overhead копирования.

javascript
// Transferable objects move data to a worker WITHOUT copying
// (the original becomes unusable) — much faster for large ArrayBuffers

// Create a large buffer
const buffer = new ArrayBuffer(1024 * 1024 * 10);  // 10MB
const view = new Float64Array(buffer);
view[0] = 3.14;

// Transfer the buffer (second arg = transfer list)
worker.postMessage({ data: buffer }, [buffer]);

// After transfer, 'buffer' is detached (length becomes 0)
console.log(buffer.byteLength);  // 0 — ownership moved to worker

// Worker receives it normally:
// self.onmessage = (e) => {
//   const buf = e.data.data;  // full 10MB, no copy
// };

// Transferable types: ArrayBuffer, MessagePort, ImageBitmap
// Use for: large datasets, image data, audio buffers

SharedArrayBuffer и Atomics

SharedArrayBuffer позволяет истинную разделяемую память между потоками (без копирования), а Atomics предоставляет thread-safe операции (add, load, store, compareExchange, wait/notify) на ней. Это включает высокопроизводительные параллельные алгоритмы в JS. Из-за проблем безопасности Spectre SharedArrayBuffer требует cross-origin isolation HTTP-заголовков — без них он отключён в современных браузерах. Используйте для WASM-интеропа и тяжёлых параллельных вычислений.

javascript
// SharedArrayBuffer: memory shared between main thread and workers
// (both can read/write simultaneously) — true shared memory

// const sharedBuffer = new SharedArrayBuffer(1024);
// const view = new Int32Array(sharedBuffer);
// worker.postMessage({ buffer: sharedBuffer });

// Atomics: thread-safe operations on SharedArrayBuffer
// const sharedArray = new Int32Array(sharedBuffer);
// Atomics.add(sharedArray, 0, 1);     // atomic increment
// Atomics.load(sharedArray, 0);       // atomic read
// Atomics.store(sharedArray, 0, 42);  // atomic write
// Atomics.compareExchange(sharedArray, 0, 42, 99);  // CAS

// Atomics.wait / notify: block and wake threads
// Worker:
//   Atomics.wait(sharedArray, 0, 0);  // block until index 0 != 0
// Main:
//   Atomics.store(sharedArray, 0, 1);
//   Atomics.notify(sharedArray, 0);   // wake waiting workers

// NOTE: Requires cross-origin isolation headers:
//   Cross-Origin-Opener-Policy: same-origin
//   Cross-Origin-Embedder-Policy: require-corp

Паттерн Worker Pool

Создание workers имеет overhead, поэтому worker pool переиспользует фиксированное число workers и ставит задачи в очередь — как пул потоков в других языках. Этот паттерн максимизирует использование CPU (один worker на ядро), избегая затрат на порождение workers для каждой задачи. Пул распределяет задачи свободным workers и ставит в очередь, когда все заняты. Необходимо для обработки множества независимых кусков работы.

javascript
// A worker pool reuses workers to avoid creation overhead
class WorkerPool {
  constructor(workerUrl, size = 4) {
    this.workers = [];
    this.queue = [];
    for (let i = 0; i < size; i++) {
      const worker = new Worker(workerUrl);
      worker.busy = false;
      this.workers.push(worker);
    }
  }

  run(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject };
      const worker = this.workers.find(w => !w.busy);
      if (worker) {
        this.execute(worker, task);
      } else {
        this.queue.push(task);  // wait for a free worker
      }
    });
  }

  execute(worker, task) {
    worker.busy = true;
    worker.onmessage = (e) => {
      worker.busy = false;
      task.resolve(e.data);
      this.next();
    };
    worker.postMessage(task.data);
  }

  next() {
    const task = this.queue.shift();
    const worker = this.workers.find(w => !w.busy);
    if (task && worker) this.execute(worker, task);
  }
}
21

Манипуляция DOM (углублённо)

querySelector

querySelector возвращает первое совпадение, querySelectorAll возвращает статический NodeList. getElementsByClassName возвращает живую HTMLCollection. NodeList поддерживает forEach; HTMLCollection — нет.

javascript
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('div.item');
all.forEach(el => console.log(el.textContent));
const live = document.getElementsByClassName('item');  // live
const static = document.querySelectorAll('.item');     // static

Создание и вставка

createElement создаёт новый элемент. appendChild добавляет как последний дочерний, prepend добавляет как первый. textContent безопаснее innerHTML, так как предотвращает XSS.

javascript
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
div.setAttribute('data-id', '42');
const parent = document.querySelector('#container');
parent.appendChild(div);
parent.prepend(div);

Делегирование событий

Делегирование событий привязывает один слушатель к родителю вместо многих к дочерним. closest находит ближайшего предка, совпадающего с селектором. Обрабатывает динамически добавленные элементы.

javascript
document.querySelector('#list').addEventListener('click', (e) => {
    const item = e.target.closest('.list-item');
    if (!item) return;
    console.log('Clicked:', item.dataset.id);
});

API classList

classList предоставляет методы для безопасной манипуляции CSS-классами. toggle возвращает true, если добавлен, false, если удалён. Чище, чем манипуляция строкой className.

javascript
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('dark-mode');
el.classList.replace('old', 'new');
if (el.classList.contains('active')) { /* ... */ }

Атрибуты Dataset

data-* атрибуты хранят пользовательские данные. dataset предоставляет camelCase-доступ: data-user-id становится dataset.userId. Значения всегда строки.

javascript
// HTML: <div data-user-id="42" data-role="admin"></div>
const el = document.querySelector('[data-user-id]');
console.log(el.dataset.userId);  // "42"
console.log(el.dataset.role);    // "admin"
el.dataset.userId = '99';
22

Canvas API

Базовое рисование

getContext("2d") возвращает 2D-контекст рендеринга. fillRect рисует заполненный прямоугольник, strokeRect — контур. Устанавливайте fillStyle/strokeStyle перед рисованием.

javascript
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);

Пути и линии

moveTo позиционирует курсор без рисования. lineTo рисует линию. closePath соединяет обратно к началу. arc рисует круги/дуги.

javascript
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 35, 25, 0, Math.PI * 2);
ctx.fill();

Текст и градиенты

createLinearGradient создаёт градиент. addColorStop определяет цвета на позициях (0-1). Устанавливайте font и textAlign перед рисованием текста.

javascript
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, 'red');
grad.addColorStop(1, 'blue');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);
ctx.font = '24px Arial';
ctx.fillText('Hello Canvas', 100, 50);

Цикл анимации

requestAnimationFrame синхронизируется с обновлением дисплея (~60fps). clearRect очищает перед каждым кадром. Отменяйте через cancelAnimationFrame.

javascript
let x = 0;
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'green';
    ctx.fillRect(x, 50, 30, 30);
    x += 2;
    if (x > canvas.width) x = 0;
    requestAnimationFrame(animate);
}
animate();

Манипуляция изображениями

drawImage рендерит изображения. getImageData возвращает данные пикселей как RGBA-массивы. Манипулируйте пикселями для фильтров. putImageData записывает изменённые пиксели обратно.

javascript
const img = new Image();
img.onload = () => {
    ctx.drawImage(img, 0, 0, 200, 150);
    const data = ctx.getImageData(0, 0, 200, 150);
    for (let i = 0; i < data.data.length; i += 4)
        data.data[i] = 255 - data.data[i];
    ctx.putImageData(data, 0, 0);
};
img.src = 'photo.jpg';
23

WebSockets

Базовый WebSocket

WebSocket обеспечивает полнодуплексную связь через одно TCP-соединение. onopen срабатывает при подключении, onmessage при поступлении данных. Всегда обрабатывайте onerror.

javascript
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => { ws.send('Hello Server'); };
ws.onmessage = (e) => { console.log('Received:', e.data); };
ws.onclose = () => console.log('Disconnected');
ws.onerror = (err) => console.error('Error:', err);

Отправка и приём JSON

Данные WebSocket передаются как строки или бинарные. JSON.stringify/parse включает структурированный обмен данными. Поле type обеспечивает маршрутизацию сообщений.

javascript
ws.onopen = () => {
    ws.send(JSON.stringify({ type: 'message', text: 'Hello' }));
};
ws.onmessage = (e) => {
    const data = JSON.parse(e.data);
    switch (data.type) {
        case 'message': console.log(data.text); break;
    }
};

Переподключение

WebSocket-соединения могут разрываться. Экспоненциальный backoff (2^retries) предотвращает перегрузку сервера. Ограничьте задержку 30с. Сбрасывайте счётчик попыток при успехе.

javascript
class ReconnectingWS {
    constructor(url) { this.url = url; this.retries = 0; this.connect(); }
    connect() {
        this.ws = new WebSocket(this.url);
        this.ws.onopen = () => { this.retries = 0; };
        this.ws.onclose = () => {
            const delay = Math.min(1000 * 2 ** this.retries++, 30000);
            setTimeout(() => this.connect(), delay);
        };
    }
}

Бинарные данные

WebSocket поддерживает бинарные данные через ArrayBuffer. Установите binaryType в arraybuffer. DataView предоставляет типизированный доступ. Бинарные данные эффективнее для числовых данных.

javascript
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
ws.send(buffer);
ws.binaryType = 'arraybuffer';
ws.onmessage = (e) => {
    if (e.data instanceof ArrayBuffer) {
        const v = new DataView(e.data);
        console.log(v.getInt32(0));
    }
};

Heartbeat

Heartbeat обнаруживает устаревшие соединения. Отправляйте ping и ожидайте pong. Если нет pong в течение таймаута, закрывайте и переподключайтесь. readyState проверяет статус соединения.

javascript
setInterval(() => {
    if (ws.readyState === WebSocket.OPEN)
        ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
    if (Date.now() - lastPong > 60000) ws.close();
}, 10000);
24

Service Workers

Регистрация

Service workers работают в отдельном потоке, перехватывая сетевые запросы. Регистрация должна происходить на HTTPS или localhost. Расположение SW-файла определяет scope.

javascript
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(reg => console.log('Registered:', reg.scope))
        .catch(err => console.error('Failed:', err));
}

Кэширование

Стратегия cache-first: обслуживать из кэша, fallback к сети. install предварительно кэширует ресурсы. fetch перехватывает запросы. Другие стратегии: network-first, stale-while-revalidate.

javascript
const CACHE = 'app-v1';
self.addEventListener('install', (e) => {
    e.waitUntil(caches.open(CACHE)
        .then(c => c.addAll(['/', '/index.html', '/style.css'])));
});
self.addEventListener('fetch', (e) => {
    e.respondWith(caches.match(e.request)
        .then(cached => cached || fetch(e.request)));
});

Фоновая синхронизация

Background sync откладывает действия до восстановления подключения. Событие sync срабатывает при доступности сети. Храните ожидающие действия в IndexedDB.

javascript
navigator.serviceWorker.ready.then(reg =>
    reg.sync.register('send-messages'));
self.addEventListener('sync', (e) => {
    if (e.tag === 'send-messages')
        e.waitUntil(sendPendingMessages());
});

Push-уведомления

Push-уведомления работают даже при закрытом приложении. subscribe регистрирует в push-сервисе с использованием VAPID-ключей. userVisibleOnly требует показа уведомления.

javascript
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: vapidPublicKey
});
self.addEventListener('push', (e) => {
    const data = e.data.json();
    e.waitUntil(self.registration.showNotification(data.title, {
        body: data.body
    }));
});

Обновление и активация

skipWaiting немедленно активирует новый SW. activate очищает старые кэши. clients.claim сразу берёт управление. Версионирование имени кэша запускает обновления.

javascript
self.addEventListener('install', (e) => {
    self.skipWaiting();
});
self.addEventListener('activate', (e) => {
    e.waitUntil(caches.keys().then(keys =>
        Promise.all(keys.filter(k => k !== CACHE)
            .map(k => caches.delete(k)))));
    self.clients.claim();
});
25

IndexedDB

Открытие базы данных

IndexedDB — NoSQL база данных в браузере. onupgradeneeded срабатывает при изменении версии, используется для создания object stores. keyPath определяет первичный ключ.

javascript
const req = indexedDB.open('MyDatabase', 1);
req.onupgradeneeded = (e) => {
    const db = e.target.result;
    if (!db.objectStoreNames.contains('users'))
        db.createObjectStore('users', { keyPath: 'id' });
};
req.onsuccess = (e) => { const db = e.target.result; };

Add и Put

Транзакции группируют операции атомарно. add падает на дублирующих ключах, put перезаписывает. readwrite разрешает модификации. oncomplete срабатывает при успехе.

javascript
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice' });  // Fails if key exists
store.put({ id: 1, name: 'Bob' });   // Overwrites
tx.oncomplete = () => console.log('Saved');

Запрос данных

get извлекает по ключу. openCursor итерирует записи. cursor.continue переходит к следующей. Оберните в Promise для использования async/await.

javascript
const tx = db.transaction('users', 'readonly');
const req = tx.objectStore('users').get(1);
req.onsuccess = () => console.log(req.result);
const cursorReq = tx.objectStore('users').openCursor();
cursorReq.onsuccess = (e) => {
    const cursor = e.target.result;
    if (cursor) { console.log(cursor.value); cursor.continue(); }
};

Запросы по индексу

Индексы разрешают запросы по неключевым полям. IDBKeyRange создаёт границы. Индексы должны создаваться в onupgradeneeded.

javascript
const tx = db.transaction('users', 'readonly');
const index = tx.objectStore('users').index('name');
index.get('Alice').onsuccess = (e) =>
    console.log(e.target.result);
const range = IDBKeyRange.bound('A', 'M');
index.openCursor(range).onsuccess = (e) => {
    const cursor = e.target.result;
    if (cursor) { cursor.continue(); }
};

Delete и Clear

delete удаляет одну запись. clear удаляет все записи. deleteDatabase удаляет всю базу данных. Все модификации требуют readwrite-транзакции.

javascript
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').delete(1);  // Delete by key
tx.objectStore('users').clear();   // Clear all
indexedDB.deleteDatabase('MyDatabase');  // Delete DB
tx.oncomplete = () => console.log('Done');
26

WebRTC

Get User Media

getUserMedia запрашивает доступ к камере и микрофону. Возвращает MediaStream. srcObject назначает поток video-элементу. Требует HTTPS и разрешения пользователя.

javascript
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        document.querySelector('video').srcObject = stream;
    });

Peer Connection

RTCPeerConnection устанавливает P2P-соединения. addTrack добавляет медиа. ontrack принимает удалённый поток. ICE candidates — сетевые пути, обмениваемые через сигналинг.

javascript
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
    if (e.candidate) sendToPeer(e.candidate);
};

Offer и Answer

SDP-согласование: обмен offer/answer описывает медиаформаты. setLocalDescription устанавливает локальный SDP, setRemoteDescription — удалённый SDP.

javascript
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToPeer(offer);
// Callee:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToPeer(answer);

Data Channels

Data channels обеспечивают передачу произвольных данных через WebRTC с низкой задержкой. createDataChannel создаёт на инициаторе. ondatachannel принимает на ответчике.

javascript
const channel = pc.createDataChannel('chat');
channel.onopen = () => console.log('Connected');
channel.onmessage = (e) => console.log('Received:', e.data);
channel.send('Hello peer!');
pc.ondatachannel = (e) => {
    e.channel.onmessage = (ev) => console.log(ev.data);
};

Разделение экрана

getDisplayMedia захватывает экран, окно или вкладку. Браузер показывает picker. onended срабатывает при остановке пользователем. Всегда обрабатывайте очистку.

javascript
const stream = await navigator.mediaDevices.getDisplayMedia({
    video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
    console.log('Stopped');
27

Оптимизация производительности

Debounce и Throttle

Debounce задерживает выполнение до прекращения вызовов (хорошо для поиска). Throttle ограничивает одним разом за интервал (хорошо для скролла). Оба предотвращают избыточные вызовы.

javascript
function debounce(fn, delay) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}
function throttle(fn, limit) {
    let inThrottle;
    return (...args) => {
        if (!inThrottle) { fn(...args); inThrottle = true;
            setTimeout(() => inThrottle = false, limit); }
    };
}

Web Workers

Web Workers запускают JavaScript в отдельном потоке для CPU-интенсивных задач. Данные передаются через postMessage. Workers не могут обращаться к DOM.

javascript
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => console.log('Result:', e.data);
// worker.js
self.onmessage = (e) => {
    self.postMessage(e.data.data.map(x => x ** 2));
};

Ленивая загрузка

IntersectionObserver срабатывает при попадании элементов в viewport. data-src хранит реальный URL; src устанавливается при видимости. Снижает начальную загрузку для страниц с множеством изображений.

javascript
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
        }
    });
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

Пакетное обновление DOM

DocumentFragment пакетирует DOM-вставки в единый reflow, гораздо быстрее, чем добавление по одному. Минимизируйте layout thrashing.

javascript
const fragment = document.createDocumentFragment();
items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    fragment.appendChild(li);
});
list.appendChild(fragment);  // Single reflow

Управление памятью

WeakMap позволяет GC ключей, предотвращая утечки. Всегда удаляйте слушатели событий при удалении элементов. Устанавливайте большие объекты в null для GC.

javascript
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null;  // Allow GC
28

Безопасность (XSS/CSRF)

Предотвращение XSS

XSS инъектирует вредоносные скрипты через пользовательский ввод. Никогда не используйте innerHTML с недоверенными данными. textContent безопасен. CSP-заголовки ограничивают источники скриптов.

javascript
// BAD: vulnerable to XSS
element.innerHTML = userInput;
// GOOD: safe alternatives
element.textContent = userInput;
function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}

Предотвращение CSRF

CSRF обманом заставляет пользователей выполнять нежелательные действия. Токены гарантируют, что запросы пришли из вашего приложения. Cookies SameSite=Strict не отправляются cross-site. Используйте CSRF-токены для state-changing операций.

javascript
const token = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
fetch('/api/data', {
    method: 'POST',
    headers: { 'X-CSRF-Token': token }
});
// SameSite cookie: Set-Cookie: session=abc; SameSite=Strict

Content Security Policy

CSP ограничивает, какие ресурсы могут загружаться. default-src — fallback. script-src контролирует JavaScript. Начните с report-only mode перед принуждением.

javascript
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' https://cdn.example.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' data:">

Безопасные Cookies

HttpOnly предотвращает доступ JavaScript к cookies, смягчая XSS. Secure обеспечивает только HTTPS. SameSite=Strict предотвращает CSRF. Session cookies всегда должны использовать оба.

javascript
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// HttpOnly: not accessible via JavaScript
// Secure: only sent over HTTPS
// SameSite: Strict | Lax | None
document.cookie  // Cannot see HttpOnly cookies

Валидация ввода

Клиентская валидация улучшает UX, но не безопасность. Всегда валидируйте на сервере. Используйте DOMPurify для санитизации HTML. Whitelist разрешённых тегов.

javascript
function validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function sanitize(html) {
    return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong'] });
}
// Never trust client input - always validate on server too
29

Паттерны проектирования

Singleton

Singleton гарантирует, что существует только один экземпляр. Конструктор возвращает существующий экземпляр. Полезно для конфигурации, логирования и подключений к БД.

javascript
class Config {
    constructor() {
        if (Config.instance) return Config.instance;
        this.settings = {};
        Config.instance = this;
    }
}
const c1 = new Config();
const c2 = new Config();
console.log(c1 === c2);  // true

Observer/Pub-Sub

Паттерн Observer позволяет объектам подписываться на события. on регистрирует, emit запускает, off отписывает. Основа событийно-ориентированных архитектур.

javascript
class EventEmitter {
    constructor() { this.events = {}; }
    on(event, cb) { (this.events[event] ||= []).push(cb); }
    emit(event, data) { (this.events[event] || []).forEach(cb => cb(data)); }
    off(event, cb) { this.events[event] = this.events[event]?.filter(c => c !== cb); }
}

Factory

Factory создаёт объекты без раскрытия логики инстанцирования. Вызывающий указывает тип, фабрика решает, какой класс инстанцировать.

javascript
class Dog { speak() { return 'Woof'; } }
class Cat { speak() { return 'Meow'; } }
function AnimalFactory(type) {
    switch (type) {
        case 'dog': return new Dog();
        case 'cat': return new Cat();
    }
}

Паттерн Module

Паттерн module инкапсулирует приватное состояние с помощью замыканий. IIFE создаёт приватную область. Только возвращённые методы публичны.

javascript
const counter = (() => {
    let count = 0;  // Private
    return {
        increment: () => ++count,
        getCount: () => count
    };
})();
counter.increment();
console.log(counter.getCount());  // 1

Strategy

Паттерн strategy инкапсулирует взаимозаменяемые алгоритмы. Контекст делегирует выбранной стратегии. Избегает больших цепочек if/else.

javascript
const strategies = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
};
function calculate(strategy, a, b) {
    return strategies[strategy]?.(a, b);
}
30

Распространённые подводные камни

Привязка this

this определяется тем, как вызывается функция. Стрелочные функции наследуют this из объемлющей области. Методы теряют this при отсоединении. Используйте bind.

javascript
const obj = {
    name: 'Alice',
    greet: function() { console.log(this.name); },
    arrow: () => console.log(this.name)
};
obj.greet();   // Alice
obj.arrow();   // undefined
const fn = obj.greet;
fn();  // undefined (lost binding)

Плавающая точка

JavaScript использует IEEE 754 плавающую точку. Используйте Number.EPSILON для сравнений или умножайте на степени 10 для работы с целыми числами.

javascript
console.log(0.1 + 0.2);        // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
const eps = Number.EPSILON;
console.log(Math.abs(0.1 + 0.2 - 0.3) < eps);  // true

== против ===

== выполняет приведение типов, что приводит к неожиданным результатам. === проверяет и тип, и значение без приведения. Всегда используйте === и !==.

javascript
console.log(0 == false);      // true
console.log('' == false);     // true
console.log(null == undefined); // true
console.log(0 === false);     // false
console.log('' === false);    // false
// Always use ===

Замыкания в циклах

var имеет функциональную область, поэтому все замыкания разделяют одну переменную. let имеет блочную область, создавая новую привязку на каждой итерации. Всегда используйте let/const.

javascript
// BUG: all print 3
for (var i = 0; i < 3; i++)
    setTimeout(() => console.log(i), 100);
// FIX: let
for (let i = 0; i < 3; i++)
    setTimeout(() => console.log(i), 100);

Обработка async-ошибок

Необработанные отклонения promise могут крашить Node.js. Всегда оборачивайте await в try/catch или используйте .catch(). Слушайте события unhandledRejection.

javascript
// BUG: unhandled rejection
async function fetchData() {
    const data = await fetch('/api');
    return data.json();
}
// FIX: try/catch
async function fetchDataSafe() {
    try { return await (await fetch('/api')).json(); }
    catch (err) { console.error('Failed:', err); return null; }
}

Связанные сниппеты JavaScript

Copy-paste ready code for common tasks.

Методы массивов map, filter, reduce

Цепочки map, filter, reduce, find, some и every над массивами.

Дедупликация массива

Дедупликация массива с помощью Set.

Глубокое клонирование

Глубокое клонирование объектов с поддержкой распространённых типов данных.

Функция debounce

Ожидает период времени после срабатывания события перед выполнением; сбрасывает таймер при повторном срабатывании во время ожидания.

Функция throttle

Ограничивает выполнение функции не более одного раза за интервал времени.

Управление конкурентностью Promise.all

Исполнитель Promise с ограничением конкурентности.

Обработка ошибок async/await

Обёртка асинхронных функций для единообразного перехвата исключений.

Обёртка Fetch

Обёртка fetch с таймаутом, обработкой ошибок и разбором JSON.

Операции с localStorage

Обёртка localStorage с временем истечения и поддержкой JSON.

Операции с Cookie

Обёртка операций чтения, записи и удаления Cookie.

Разбор параметров URL

Разбор строки запроса URL в объект.

Форматирование даты

Форматирование даты в указанную строку.

Форматирование денег

Форматирование числа в денежную строку с разделением тысяч.

Генерация случайных чисел

Генерация случайных чисел и случайных строк в диапазоне.

Преобразование цвета

Преобразование между цветами RGB и HEX.

Генерация UUID

Генерация уникального идентификатора, совместимого с UUID v4.

Усечение строки

Усечение строки с добавлением многоточия.

Выравнивание массива

Преобразование многомерного массива в одномерный.

Слияние объектов

Глубокое слияние нескольких объектов.

Проверка типов

Точное определение типов данных JavaScript.

Делегирование событий

Реализация делегирования событий через всплытие событий.

Манипуляции с DOM

Динамическое создание и манипулирование элементами DOM.

Валидация форм

Набор распространённых правил валидации форм.

Загрузка файлов

Обёртка загрузки файлов с поддержкой прогресса и чанкования.

Ленивая загрузка изображений

Реализация ленивой загрузки изображений с IntersectionObserver.

Копирование в буфер обмена

Кроссбраузерный метод копирования в буфер обмена.

Fullscreen API

Обёртка полноэкранных операций браузера.

Геолокация

Получение информации о геолокации пользователя.

Web Worker

Создание Web Worker для выполнения длительных задач.

Service Worker

Регистрация Service Worker для офлайн-кэширования.

Операции с IndexedDB

Обёртка CRUD-операций IndexedDB.

Рисование на Canvas

Базовый пример рисования на Canvas.

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.