Начало работы
Hello World и комментарии
JavaScript работает в браузерах и Node.js. console.log() — основной вывод для отладки. JSDoc-комментарии (/** */) предоставляют информацию о типах и документацию для IDE. Используйте 'use strict' или ES-модули для более безопасного парсинга. Комментарии игнорируются во время выполнения.
// 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'>.
"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 предотвращает использование переменных до объявления.
// 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.
// 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).
// 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Строки
Строковые методы
Строки неизменяемы — методы возвращают новые строки. slice() поддерживает отрицательные индексы (с конца); substring() — нет. replace() заменяет только первое совпадение; используйте replaceAll() (ES2021) для всех. at() (ES2022) допускает отрицательные индексы. split() + join() — идиоматичный способ заменить символы в строке.
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 и т.д.
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() для проверки совпадения шаблона без извлечения.
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 кодовые единицы.
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Числа и математика
Числа и операторы
В JavaScript один числовой тип (64-битный float) — нет отдельного int/float. BigInt (суффикс n) обрабатывает целые числа за пределами 2^53. Арифметика с плавающей точкой имеет проблемы точности (0.1 + 0.2 !== 0.3) — используйте Number.EPSILON для сравнений. ** — оператор возведения в степень (ES2016).
// 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 не принимают массивы напрямую — разворачивайте через ....
// 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.
// 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Структуры данных
Массивы
Массивы динамические, упорядоченные и могут содержать смешанные типы. push/pop — O(1); shift/unshift/splice — O(n). find()/findIndex() принимают функцию-предикат. forEach() ничего не возвращает; используйте map() для преобразования. Массивы — объекты — typeof [] равно 'object'. Используйте Array.isArray() для проверки.
// 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() выравнивает вложенные массивы.
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 итерирует ключи (включая унаследованные).
// 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 переопределяет дубликаты ключей (последний выигрывает). Деструктуризация в параметрах функций мощна для опциональных конфигов.
// 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 copyMap, Set, WeakMap
Map допускает любой тип ключа (объекты, не только строки) и сохраняет порядок вставки — в отличие от обычных объектов. Set хранит уникальные значения — идеально для дедупликации. Ключи WeakMap/WeakSet слабо ссылаются (могут быть собраны сборщиком мусора), предотвращая утечки памяти. Используйте Map, когда нужны нестроковые ключи или частые добавления/удаления.
// 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Управление потоком
If / Else и тернарный оператор
Используйте if/else для сложной логики; тернарный — для простого выбора значения. && и || коротко замыкаются (полезно для значений по умолчанию/условий). ?? (nullish coalescing) проверяет только null/undefined, в отличие от ||, проверяющего все ложные значения. ?. (optional chaining) безопасно обращается к вложенным с войствам без ошибок.
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 для дискретных значений. Современный код иногда предпочитает таблицы поиска в объектах.
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.
// 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-потоков.
// 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;
}Функции
Объявления и выражения функций
Объявления функций поднимаются (можно вызвать до определения); выражения — нет. Стрелочные функции лаконичны и не имеют собственного 'this' (наследуют из объемлющей области). IIFE создают приватные области (менее нужны с модулями). Функции первого класса — передавайте как аргументы, возвращайте, храните в переменных.
// 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'. Используйте обычные функции для методов объектов.
// 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 — замыкание. Внутренняя функция хранит ссылку на внешние переменные, а не копию.
// 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-паттерн.
// 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)ООП и классы
Классы и конструктор
ES6-классы — синтаксический сахар над прототипами. Приватные поля (#name) — ES2022 и действительно приватные (в отличие от соглашения _name). Геттеры/сеттеры позволяют вычисляемые свойства. Статические члены принадлежат классу, а не экземплярам. Поля класса (name = value) инициализируют свойства экземпляра без конструктора.
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 проверяет цепочку прототипов. Полиморфизм работает через переопределение методов.
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) опасна — может сломать код. Предпочитайте композицию глубокому наследованию.
// 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);
}Обработка ошибок
Try / Catch / Finally
try/catch/finally обрабатывает исключения. catch связывает объект ошибки (имеющий .message и .stack). Используйте instanceof для различной обработки конкретных типов ошибок. Всегда перевыбрасывайте неизвестные ошибки после обработки ожидаемых. Создавайте пользовательские ошибки, наследуя Error для специфичной обработки ошибок приложения.
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 }) сохраняет исходную ошибку для отладки. Хорошая иерархия ошибок делает обработку точной.
// 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 });
}Асинхронность и Promise
Колбэки
Колбэки — функции, передаваемые для вызова позже. Конвенция error-first (err, data) — стандарт в Node.js. Вложенные колбэки создают 'callback hell' — глубоко вложенный, трудный для чтения код. Promise и async/await решают это. setTimeout/setInterval — распространённые API на колбэках.
// 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); // stopPromise
Promise представляют будущие значения с тремя состояниями: pending, fulfilled, rejected. .then() обрабатывает успех, .catch() — ошибки, .finally() выполняется всегда. Promise.all() ждёт все (fail fast); allSettled() ждёт все (никогда не падает); race() возвращает первый завершённый; any() возвращает первый успешный.
// 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-модулях.
// 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)));
}Манипуляция DOM
Выбор и модификация элементов
querySelector/querySelectorAll (CSS-селекторы) — современный способ выбора элементов. textContent безопаснее innerHTML (предотвращает XSS). classList предоставляет add/remove/toggle/contains для классов. dataset обращается к data-* атрибутам. Всегда санитизируйте пользовательский ввод перед установкой innerHTML.
// 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() останавливает всплытие.
// 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);Модули и JSON
ES-модули
ES-модули (import/export) — современный стандарт, поддерживается в браузерах и Node.js. Default-экспорт (один на модуль) против именованных экспортов (несколько). Динамический import() включает ленивую загрузку. Модули всегда в строгом режиме и имеют собственную область видимости. Используйте type='module' в HTML script-тегах.
// 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 defaultJSON
JSON.stringify() преобразует JS-значения в JSON-строки; JSON.parse() обращает. Используйте replacers/revivers для фильтрации или преобразования во время конвертации. JSON не поддерживает функции, undefined, Date (становятся строками) или циклические ссылки. fetch().json() автоматически парсит JSON-ответы. Всегда оборачивайте JSON.parse в try/catch для недоверенного ввода.
// 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 предоставляет локализованное форматирование.
// 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日"Возможности ES6+
let, const и блочная область видимости
Предпочитайте const по умолчанию, let при необходимости переназначения, избегайте var полностью. const предотвращает переназначение, но объекты/массивы всё ещё изменяемы. let и const имеют блочную область видимости и живут во Temporal Dead Zone до объявления (в отличие от var, который поднимается как undefined). Это предотвращает множество тонких багов.
// 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' ссылался на объект (используйте обычные методы).
// 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 и параметрах функций.
// 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-идиомы.
// 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.
// 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.
// 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!)События и обработка событий
Основы addEventListener
addEventListener — современный способ привязки событий — допускает несколько обработчиков, поддерживает делегирование событий и предлагает опции once, passive и capture. Всегда храните ссылку на обработчик, если нужно удалить его позже (анонимные функции нельзя удалить). Объект события содержит target, currentTarget, preventDefault() и stopPropagation().
// 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) для фильтрации. Это гораздо эффективнее привязки к каждому дочернему элементу и автоматически обрабатывает динамически добавленные элементы. Компромисс: родитель должен быть общим предком, который всегда существует.
// 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) для обработчиков, которые должны выполниться до дочерних.
<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' для избежания коллизий. Это основа многих фреймворков пользовательских элементов.
// 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' — при потере фокуса — выбирайте в зависимости от момента валидации.
// 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 blurFetch API и AJAX
Базовый fetch (GET)
fetch() — современная замена XMLHttpRequest — на основе promise и чище. Важно: fetch отклоняет только при сетевых ошибках, НЕ при HTTP-ошибках (404, 500). Всегда проверяйте response.ok (статус 200-299) перед парсингом. Используйте async/await для читаемого последовательного кода. response.json() асинхронный, потому что читает поток тела.
// 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 частично обновляет.
// 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 запросов.
// 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.
// 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.
// 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Веб-хранилище (LocalStorage и SessionStorage)
Основы localStorage и sessionStorage
localStorage хранит бесконечно; sessionStorage очищается при закрытии вкладки. Оба хранят только строки — используйте JSON.stringify/parse для объектов. Хранилище синхронное и блокирует основной поток, поэтому избегайте хранения больших данных. Доступно во всех современных браузерах, но может быть отключено в режиме приватного просмотра. Ёмкость ~5-10МБ на origin.
// 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 может выбросить исключение на повреждённых данных, а квота может быть превышена.
// 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.
// 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 приложений, больших кэшей или сложных клиентских данных.
// 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.
// 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Таймеры (setTimeout, setInterval)
setTimeout и setInterval
setTimeout запускает колбэк один раз после задержки; setInterval запускает его повторно. Оба возвращают ID для отмены через clearTimeout/clearInterval. Таймеры не точные — это минимальные задержки, зависящие от цикла событий, видимости вкладки (throttle в фоновых вкладках) и доступности основного потока. Задержки менее 4мс могут быть ограничены до 4мс во вложенных таймерах.
// 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 может накапливать вызовы, если обработчик занимает больше времени, чем интервал, вызывая проблемы с производительностью.
// 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 по завершении.
// 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 = 'ограничить частоту вызовов'.
// 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.
// 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);
}
}
}Map, Set и WeakMap
Map (ключ-значение с любым типом ключа)
Map — полноценная коллекция ключ-значение, где ключи могут быть любого типа (объекты, функции, числа) — в отличие от Objects, которые приводят ключи к строкам. Map сохраняет порядок вставки, имеет свойство .size и напрямую итерируем. Используйте Map, когда нужны нестроковые ключи, частые добавлен ия/удаления или когда коллекция не является record/DTO. Используйте Object для данных фиксированной формы.
// 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/removeSet (уникальные значения)
Set хранит уникальные значения — идеально для дедупликации и проверки принадлежности. Преобразование массива в Set и обратно ([...new Set(arr)]) — идиоматичный способ удаления дубликатов. Set.has() — O(1) против Array.includes() — O(n), поэтому используйте Set для больших коллекций, которые часто проверяете. Set не имеет map/filter — разворачивайте в массив сначала.
// 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. Используйте для кэширования/метаданных, привязанных к времени жизни объекта.
// 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 становятся строковым типом при преобразовании.
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().
// 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); }Генераторы и итераторы
Итераторы и Symbol.iterator
Итератор имеет метод next(), возвращающий {value, done}. Итерируемый объект реализует Symbol.iterator, который возвращает итератор. Встроенные итерируемые (массивы, строки, Map, Set) работают с for...of, spread (...), деструктуризацией и Array.from(). Реализация Symbol.iterator позволяет вашим пользовательским объектам работать со всеми этими языковыми возможностями бесшовно.
// 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-паттернов корутин.
// 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'. Значения делегированного генератора выводятся по одному, как если бы они были частью внешнего генератора. Это стандартный способ составления генераторов рекурсивно.
// 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() не может передать значение (нечего его принимать).
// 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.
// 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;
}
}Proxy и Reflect
Основы Proxy (перехват операций)
Proxy позволяет перехватывать и настраивать фундаментальные операции над объектом — get, set, has, deleteProperty, ownKeys и более. Объект handler определяет 'ловушки' (как геттеры/сеттеры, но для всех свойств). Сценарии использования: валидация, логирование, значения по умолчанию, контроль доступа, реактивные системы (Vue 3 использует Proxy для реактивности). Возвращайте true из set для индикации успеха в строгом режиме.
// 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 там, где абстракция оправдана, а не для каждого объекта.
// 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.
// 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 с ограничениями.
// 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 automaticallyWeb Workers
Создание Web Worker
Web Workers запускают JavaScript в отдельном фоновом потоке, обеспечивая истинный параллелизм без блокировки UI. Основной поток и worker общаются через postMessage (данные копируются/structured-cloned, не разделяются). Workers не могут обращаться к DOM или объекту window — они изолированы. Используйте workers для CPU-интенсивных задач вроде обработки и зображений, парсинга больших файлов или сложных вычислений.
// 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.
// 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 fileTransferable Objects (zero-copy)
Обычно postMessage копирует данные (structured clone), что медленно для больших буферов. Transfer list (второй аргумент) ПЕРЕДАЁТ владение ArrayBuffer/MessagePort/ImageBitmap в worker с нулевым копированием — почти мгновенно независимо от размера. Исходный буфер становится detached (непригодным). Используйте для больших наборов данных, обработки изображений или аудио, чтобы избежать overhead копирования.
// 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 buffersSharedArrayBuffer и Atomics
SharedArrayBuffer позволяет истинную разделяемую память между потоками (без копирования), а Atomics предоставляет thread-safe операции (add, load, store, compareExchange, wait/notify) на ней. Это включает высокопроизводительные параллельные алгоритмы в JS. Из-за проблем безопасности Spectre SharedArrayBuffer требует cross-origin isolation HTTP-заголовков — без них он отключён в современных браузерах. Используйте для WASM-интеропа и тяжёлых параллельных вычислений.
// 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 и ставит в очередь, когда все заняты. Необходимо для обработки множества независимых кусков работы.
// 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);
}
}Манипуляция DOM (углублённо)
querySelector
querySelector возвращает первое совпадение, querySelectorAll возвращает статический NodeList. getElementsByClassName возвращает живую HTMLCollection. NodeList поддерживает forEach; HTMLCollection — нет.
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.
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 находит ближайшего предка, совпадающего с селектором. Обрабатывает динамически добавленные элементы.
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.
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. Значения всегда строки.
// 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';Canvas API
Базовое рисование
getContext("2d") возвращает 2D-контекст рендеринга. fillRect рисует заполненный прямоугольник, strokeRect — контур. Устанавливайте fillStyle/strokeStyle перед рисованием.
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 рисует круги/дуги.
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 перед рисованием текста.
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.
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 записывает изменённые пиксели обратно.
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';WebSockets
Базовый WebSocket
WebSocket обеспечивает полнодуплексную связь через одно TCP-соединение. onopen срабатывает при подключении, onmessage при поступлении данных. Всегда обрабатывайте onerror.
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 обеспечивает маршрутизацию сообщений.
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с. Сбрасывайте счётчик попыток при успехе.
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 предоставляет типизированный доступ. Бинарные данные эффективнее для числовых данных.
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 проверяет статус соединения.
setInterval(() => {
if (ws.readyState === WebSocket.OPEN)
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
if (Date.now() - lastPong > 60000) ws.close();
}, 10000);Service Workers
Регистрация
Service workers работают в отдельном потоке, перехватывая сетевые запросы. Регистрация должна происходить на HTTPS или localhost. Расположение SW-файла определяет scope.
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.
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.
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 требует показа уведомления.
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 сразу берёт управление. Версионирование имени кэша запускает обновления.
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();
});IndexedDB
Открытие базы данных
IndexedDB — NoSQL база данных в браузере. onupgradeneeded срабатывает при изменении версии, используется для создания object stores. keyPath определяет первичный ключ.
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 срабатывает при успехе.
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.
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.
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-транзакции.
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');WebRTC
Get User Media
getUserMedia запрашивает доступ к камере и микрофону. Возвращает MediaStream. srcObject назначает поток video-элементу. Требует HTTPS и разрешения пользователя.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
document.querySelector('video').srcObject = stream;
});Peer Connection
RTCPeerConnection устанавливает P2P-соединения. addTrack добавляет медиа. ontrack принимает удалённый поток. ICE candidates — сетевые пути, обмениваемые через сигналинг.
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.
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 принимает на ответчике.
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 срабатывает при остановке пользователем. Всегда обрабат ывайте очистку.
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
console.log('Stopped');Оптимизация производительности
Debounce и Throttle
Debounce задерживает выполнение до прекращения вызовов (хорошо для поиска). Throttle ограничивает одним разом за интервал (хорошо для скролла). Оба предотвращают избыточные вызовы.
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.
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 устанавливается при видимости. Снижает начальную загрузку для страниц с множе ством изображений.
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.
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.
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null; // Allow GCБезопасность (XSS/CSRF)
Предотвращение XSS
XSS инъектирует вредоносные скрипты через пользовательский ввод. Никогда не используйте innerHTML с недоверенными данными. textContent безопасен. CSP-заголовки ограничивают источники скриптов.
// 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 операций.
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=StrictContent Security Policy
CSP ограничивает, какие ресурсы могут загружаться. default-src — fallback. script-src контролирует JavaScript. Начните с report-only mode перед принуждением.
<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 всегда должны использовать оба.
// 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 разрешённых тегов.
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Паттерны проектирования
Singleton
Singleton гарантирует, что существует только один экземпляр. Конструктор возвращает существующий экземпляр. Полезно для конфигурации, логирования и подключений к БД.
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); // trueObserver/Pub-Sub
Паттерн Observer позволяет объектам подписываться на события. on регистрирует, emit запускает, off отписывает. Основа событийно-ориентированных архитектур.
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 создаёт объекты без раскрытия логики инстанцирования. Вызывающий указывает тип, фабрика решает, какой класс инстанцировать.
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 создаёт приватную область. Только возвращённые методы публичны.
const counter = (() => {
let count = 0; // Private
return {
increment: () => ++count,
getCount: () => count
};
})();
counter.increment();
console.log(counter.getCount()); // 1Strategy
Паттерн strategy инкапсулирует взаимозаменяемые алгоритмы. Контекст делегирует выбранной стратегии. Избегает больших цепочек if/else.
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);
}Распространённые подводные камни
Привязка this
this определяется тем, как вызывается функция. Стрелочные функции наследуют this из объемлющей области. Методы теряют this при отсоединении. Используйте bind.
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 для работы с целыми числами.
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== против ===
== выполняет приведение типов, что приводит к неожиданным результатам. === проверяет и тип, и значение без приведения. Всегда используйте === и !==.
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.
// 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.
// 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?