Иллюзия величия: Почему примитивы умеют вызывать методы (и как это работает)
Вы когда-нибудь замечали странность?
javascript
const str = "hello";
console.log(str.toUpperCase()); // "HELLO"
const num = 42;
console.log(num.toString()); // "42"
const bool = true;
console.log(bool.toString()); // "true"
Вроде бы ничего удивительного. Но подождите. "hello" - это же примитив, строка. У примитивов не может быть методов! Или может?
Добро пожаловать в одну из самых элегантных иллюзий JavaScript - механизм, который заставляет примитивы вести себя как объекты. Это не магия, это тщательно продуманная система объектов-обёрток.
Часть 1. Великий парадокс: примитивы без свойств
Начнём с основ. В JavaScript есть примитивы: string, number, boolean, symbol, bigint, null, undefined.
javascript
const str = "hello";
console.log(str.length); // 5 (откуда? у строки нет свойств!)
console.log(str.unknown); // undefined (но ошибки нет)
У примитивов не может быть свойств. Попробуем добавить:
javascript
const str = "hello";
str.newProp = "world";
console.log(str.newProp); // undefined (не сохранилось!)
Но почему же тогда str.length работает? И как мы можем вызывать str.toUpperCase()?
Часть 2. Объекты-обёртки: Тайные помощники
За кулисами JavaScript создаёт временные объекты-обёртки, когда вы пытаетесь обратиться к свойству или методу примитива.
javascript
const str = "hello";
// То, что вы пишете:
str.toUpperCase();
// То, что происходит на самом деле (упрощённо):
// 1. Создаётся временный объект String
const temp = new String(str);
// 2. У этого объекта вызывается метод
const result = temp.toUpperCase();
// 3. Временный объект уничтожается
// 4. Вам возвращается результат
То есть "hello".toUpperCase() - это красивая ложь. На самом деле это:
javascript
new String("hello").toUpperCase()
Часть 3. Три обёртки: String, Number, Boolean
Для каждого примитива есть своя объектная обёртка.
3.1 String - обёртка для строк
javascript
const str = "hello";
const strObj = new String("hello");
console.log(typeof str); // "string"
console.log(typeof strObj); // "object"
console.log(str === strObj); // false (разные типы)
console.log(str == strObj); // true (нестрогое сравнение)
3.2 Number - обёртка для чисел
javascript
const num = 42;
const numObj = new Number(42);
console.log(typeof num); // "number"
console.log(typeof numObj); // "object"
console.log(num.toFixed(2)); // "42.00" (примитив → обёртка → метод)
3.3 Boolean - обёртка для булевых
javascript
const bool = true;
const boolObj = new Boolean(true);
console.log(typeof bool); // "boolean"
console.log(typeof boolObj); // "object"
// Важное отличие
if (boolObj) {
console.log("Объект всегда true!");
}
Часть 4. Жизненный цикл временного объекта
Давайте проследим, что происходит, когда вы пишете "hello".length.
javascript
// Шаг 1: Вы обращаетесь к свойству примитива
const str = "hello";
const len = str.length;
// Шаг 2: JavaScript понимает, что str - примитив
// Шаг 3: Создаётся временный объект String
const temp = new String(str);
// Шаг 4: У временного объекта читается свойство length
const result = temp.length;
// Шаг 5: Временный объект уничтожается (становится мусором)
// Шаг 6: Результат присваивается переменной
console.log(result); // 5
Важно: Этот временный объект живёт ровно одну операцию.
javascript
const str = "hello";
str.customProp = "world"; // создался temp, добавили свойство, temp умер
console.log(str.customProp); // новый temp, свойство потеряно → undefined
Часть 5. Явное создание обёрток (и почему не надо)
Технически вы можете создавать объекты-обёртки сами:
javascript
const num = new Number(42);
const str = new String("hello");
const bool = new Boolean(true);
Но почти никогда не делайте этого. Почему?
Проблема 1: Сравнение
javascript
const num1 = 42;
const num2 = new Number(42);
console.log(num1 === num2); // false (разные типы)
console.log(num1 == num2); // true (нестрогое, но опасно)
Проблема 2: Булевы обёртки всегда true
javascript
const falseObj = new Boolean(false);
if (falseObj) {
console.log("Это выполнится!"); // Упс!
}
console.log(falseObj === false); // false
console.log(falseObj == false); // true (но в if всё равно true)
Проблема 3: Производительность
javascript
// Плохо (создаёт объекты)
const num = new Number(42);
for (let i = 0; i < 1000000; i++) {
num + 1; // Обёртка не нужна
}
// Хорошо (примитив)
const num = 42;
for (let i = 0; i < 1000000; i++) {
num + 1;
}
Когда всё же нужно использовать new Number? Практически никогда. Единственное исключение - если вам нужно сохранить свойство на числе (но это странное требование).
Часть 6. Методы примитивов: Полный список
6.1 Строковые методы
javascript
const str = "JavaScript";
// Изменение регистра
str.toUpperCase(); // "JAVASCRIPT"
str.toLowerCase(); // "javascript"
// Поиск
str.indexOf("Script"); // 4
str.includes("Java"); // true
str.startsWith("Java"); // true
str.endsWith("pt"); // true
// Извлечение
str.slice(0, 4); // "Java"
str.substring(4, 10); // "Script"
str.substr(4, 6); // "Script" (устарел)
// Замена
str.replace("Java", "Type"); // "TypeScript"
str.replaceAll("a", "o"); // "JovoScript"
// Разделение
str.split(""); // ["J","a","v","a","S","c","r","i","p","t"]
str.split(" "); // ["JavaScript"]
// Обрезка
" hello ".trim(); // "hello"
" hello ".trimStart(); // "hello "
" hello ".trimEnd(); // " hello"
// Доступ по индексу
str[0]; // "J"
str.charAt(0); // "J"
str.charCodeAt(0); // 74 (код символа)
// Повторение
"ha".repeat(3); // "hahaha"
6.2 Числовые методы
javascript
const num = 123.456;
// Округление и форматирование
num.toFixed(2); // "123.46" (строка!)
num.toPrecision(4); // "123.5" (строка!)
// Экспоненциальная запись
num.toExponential(2); // "1.23e+2"
// Преобразование в строку с системой счисления
const binary = 255;
binary.toString(2); // "11111111" (двоичная)
binary.toString(16); // "ff" (шестнадцатеричная)
// Проверки
Number.isNaN(NaN); // true
Number.isFinite(42); // true
Number.isInteger(42); // true
Number.isInteger(42.5); // false
// Константы
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.EPSILON; // 2.220446049250313e-16
6.3 Булевы методы
javascript
const bool = true;
bool.toString(); // "true"
bool.valueOf(); // true
// У Boolean нет много методов, это просто обёртка
Часть 7. Нюансы и подводные камни
7.1 null и undefined не имеют обёрток
javascript
null.toString(); // TypeError: Cannot read property 'toString' of null
undefined.toString(); // TypeError: Cannot read property 'toString' of undefined
7.2 Числовые литералы и методы (осторожно с точкой)
javascript
// ❌ Ошибка (точка воспринимается как десятичный разделитель)
42.toString();
// ✅ Правильно (две точки или скобки)
42..toString();
(42).toString();
// Почему так? Парсер видит "42." как число с плавающей точкой
// Вторая точка — вызов метода
7.3 Примитивы и new
javascript
const str1 = "hello";
const str2 = new String("hello");
console.log(typeof str1); // "string"
console.log(typeof str2); // "object"
// instanceof работает только с объектами
console.log(str1 instanceof String); // false
console.log(str2 instanceof String); // true
// Но valueOf возвращает примитив
console.log(str2.valueOf()); // "hello"
console.log(str2 + " world"); // "hello world" (автоматическое преобразование)
7.4 Добавление методов в обёртки (никогда так не делайте!)
javascript
// Технически можно, но НЕ НАДО
String.prototype.reverse = function() {
return this.split('').reverse().join('');
};
console.log("hello".reverse()); // "olleh"
// Проблема: загрязнение глобального пространства
// Потенциальные конфликты с будущими версиями JS
// Другие библиотеки могут сломаться
Часть 8. Производительность: примитивы vs обёртки
javascript
// Тест производительности
console.time("primitive");
for (let i = 0; i < 10000000; i++) {
const str = "hello";
str.toUpperCase();
}
console.timeEnd("primitive");
// ~200ms
console.time("wrapper");
for (let i = 0; i < 10000000; i++) {
const str = new String("hello");
str.toUpperCase();
}
console.timeEnd("wrapper");
// ~500ms (медленнее из-за создания объектов)
Вывод: Всегда используйте примитивы. Обёртки нужны только движку внутри.
Часть 9. Сравнение с другими языками
Python
python
# Python: строки тоже имеют методы, но нет разницы между примитивом и объектом
"hello".upper() # "HELLO"
# В Python всё - объекты
Java
java
// Java: явное различие
String str = "hello"; // примитив? нет, объект
int num = 42; // примитив
num.toString(); // Ошибка! int не имеет методов
Integer wrapper = 42; // нужна обёртка
wrapper.toString(); // работает
JavaScript - золотая середина
JavaScript берёт лучшее из двух миров: вы пишете как с примитивами (быстро), а работаете как с объектами (удобно).
Часть 10. Реальные примеры использования
10.1 Работа с датами
javascript
const date = new Date();
console.log(date.getFullYear()); // метод объекта Date
console.log(date.toString()); // строка
10.2 Регулярные выражения
javascript
const str = "JavaScript";
console.log(str.match(/[A-Z]/g)); // ["J", "S"]
console.log(str.search(/Script/)); // 4
console.log(str.replace(/Java/, "Type")); // "TypeScript"
10.3 Форматирование чисел
javascript
const price = 1234.5;
console.log(`Цена: ${price.toFixed(2)} руб.`); // "Цена: 1234.50 руб."
10.4 Валидация строк
javascript
function isValidEmail(email) {
return email.includes('@') &&
!email.startsWith('@') &&
email.lastIndexOf('.') > email.lastIndexOf('@');
}
Часть 11. Собственные методы для примитивов (полифилы)
Хотя расширять встроенные прототипы - плохая практика, иногда нужно добавить поддержку современных методов в старые браузеры.
javascript
// Полифил для String.prototype.includes (ES6)
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
if (typeof start !== 'number') start = 0;
return this.indexOf(search, start) !== -1;
};
}
// Теперь работает даже в IE
console.log("hello".includes("ell")); // true
Часть 12. Итоговая таблица
Итог: Манифест методов примитивов
- Примитивы не имеют свойств - это иллюзия, создаваемая временными объектами-обёртками.
- Три основные обёртки: String, Number, Boolean.
- Не создавайте обёртки явно - используйте примитивы.
- Временный объект живёт одну операцию - свойства, добавленные к нему, теряются.
- null и undefined не имеют обёрток - вызов метода приведёт к ошибке.
- Для чисел с точкой - используйте две точки или скобки.
- Булева обёртка всегда true - даже если внутри false.
- Примитивы быстрее и безопаснее - всегда используйте их.
Финальный тест (что выведет?):
javascript
const str = "hello";
str.test = "world";
console.log(str.test);
const num = 10;
console.log(num.toString());
console.log(10.toString()); // Что здесь?
const bool = new Boolean(false);
if (bool) {
console.log("true or false?");
}
console.log(typeof new Number(42));
console.log(typeof 42);
Ответ:
- undefined (свойство не сохранилось)
- "10" (работает)
- Ошибка синтаксиса (нужно 10..toString() или (10).toString())
- "true or false?" (объект Boolean всегда true в if)
- "object" (обёртка)
- "number" (примитив)
Методы примитивов - это элегантный компромисс между производительностью и удобством. JavaScript даёт вам скорость примитивов, но когда нужно - временно превращает их в объекты, наделяя всеми возможностями. Это как иметь друга, который умеет превращаться в супергероя ровно на секунду, чтобы помочь, а потом снова становится обычным человеком. Пользуйтесь этой силой, но помните о её временной природе.
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
Танцующие биты: Всё, что вы хотели знать о числах в JavaScript (но боялись спросить)
0.1 + 0.2 !== 0.3. Это знают даже те, кто никогда не писал ни строчки кода. Это визитная карточка JavaScript, его "ахиллесова пята" и одновременно урок смирения для всех, кто верит в математическую точность компьютеров.
Но за этой странностью скрывается целая вселенная: биты, экспоненты, специальные значения, грабли с округлением и неожиданные преобразования.
Добро пожаловать в мир чисел JavaScript - места, где typeof NaN - "number", а деление на ноль не ломает программу, а возвращает бесконечность.
Часть 1. Один формат на всё: Number
В большинстве языков программирования есть отдельные типы для целых чисел (int) и чисел с плавающей точкой (float, double). В JavaScript - один-единственный тип number.
javascript
const integer = 42; // целое
const float = 3.14; // с плавающей точкой
const negative = -10; // отрицательное
const scientific = 5e6; // 5 000 000
const hex = 0xFF; // 255 (шестнадцатеричная)
const binary = 0b1010; // 10 (двоичная)
const octal = 0o77; // 63 (восьмеричная)
Все они — 64-битные числа с плавающей точкой по стандарту IEEE 754 (также известны как "double precision").
Часть 2. Как числа хранятся в памяти (IEEE 754)
Представьте, что у вас есть 64 бита (0 или 1) для хранения числа. Они распределены так:
text
[знак][ экспонента ][ мантисса (дробная часть) ]
[1 бит][ 11 битов ][ 52 бита ]
- Знак (1 бит): 0 - положительное, 1 - отрицательное
- Экспонента (11 бит): определяет порядок числа
- Мантисса (52 бита): хранит значимые цифры
Что это значит для нас?
- Можно точно представить только числа вида n * 2^k
- Десятичные дроби вроде 0.1 представляются приближённо
javascript
// Почему 0.1 + 0.2 не равно 0.3?
console.log(0.1 + 0.2); // 0.30000000000000004
// Потому что 0.1 в двоичной системе - бесконечная дробь
// 0.1(10) = 0.0001100110011001100110011...(2)
// Компьютер отрезает лишние биты, получается погрешность
Часть 3. Специальные числовые значения
3.1 NaN (Not a Number)
javascript
console.log(0 / 0); // NaN
console.log(parseInt("hello")); // NaN
console.log(Math.sqrt(-1)); // NaN
// Странности NaN
console.log(NaN === NaN); // false (даже сам себе не равен!)
console.log(NaN == NaN); // false
// Как проверить на NaN
console.log(isNaN(NaN)); // true (но есть нюанс)
console.log(isNaN("hello")); // true (сначала приводит к числу → NaN)
console.log(Number.isNaN(NaN)); // true (безопасно)
console.log(Number.isNaN("hello")); // false (строго)
3.2 Infinity и -Infinity
javascript
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(Number.MAX_VALUE * 2); // Infinity
// Сравнение
console.log(Infinity > 1000000); // true
console.log(Infinity === Infinity); // true
// Операции с бесконечностью
console.log(Infinity + 1000); // Infinity
console.log(Infinity / Infinity); // NaN
3.3 -0 (Отрицательный ноль)
javascript
const positiveZero = 0;
const negativeZero = -0;
console.log(positiveZero === negativeZero); // true (формально равны)
console.log(1 / positiveZero); // Infinity
console.log(1 / negativeZero); // -Infinity
// Отличить можно через Object.is
console.log(Object.is(0, -0)); // false
Зачем нужен -0? Для математических операций, где важно направление (например, в анимациях и физике).
Часть 4. Безопасные целые числа
Из-за формата хранения не все целые числа можно представить точно.
javascript
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992 (ещё точно)
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (уже нет!)
// Оба дают одинаковый результат!
// Проверка безопасности
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false
Часть 5. BigInt: Спасение для больших чисел
Когда чисел Number перестаёт хватать, на сцену выходит BigInt.
javascript
const big = 9007199254740991n; // суффикс n
const alsoBig = BigInt(9007199254740991);
const huge = BigInt("9999999999999999999999");
// Операции
console.log(big + 1n); // 9007199254740992n
console.log(big * 2n); // 18014398509481982n
// Нельзя смешивать с Number без преобразования
// console.log(big + 5); // TypeError!
// Преобразование
console.log(Number(big)); // рискуете потерей точности
console.log(String(big)); // строка без потерь
// Сравнение работает
console.log(big > 100); // true
console.log(big == 9007199254740991); // true (нестрогое)
console.log(big === 9007199254740991n); // true
Часть 6. Методы чисел: Полный арсенал
6.1 Преобразование в строку
javascript
const num = 123.456;
num.toString(); // "123.456"
num.toString(2); // "1111011.011101..." (двоичная)
num.toString(8); // "173.351..." (восьмеричная)
num.toString(16); // "7b.74..." (шестнадцатеричная)
// Форматирование
num.toFixed(2); // "123.46" (округляет, возвращает строку!)
num.toPrecision(4); // "123.5" (всего 4 значащих цифры)
num.toExponential(3); // "1.235e+2"
6.2 Проверки
javascript
Number.isNaN(NaN); // true
Number.isFinite(42); // true
Number.isFinite(Infinity); // false
Number.isInteger(42); // true
Number.isInteger(42.5); // false
Number.isSafeInteger(42); // true
6.3 Константы
javascript
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324 (минимальное положительное)
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.EPSILON; // 2.220446049250313e-16 (точность)
Number.POSITIVE_INFINITY; // Infinity
Number.NEGATIVE_INFINITY; // -Infinity
Часть 7. Арифметика с подвохом
7.1 Проблема точности и её решение
javascript
// Классика
0.1 + 0.2 === 0.3; // false
// Решение 1: погрешность
function areEqual(a, b, epsilon = Number.EPSILON) {
return Math.abs(a - b) < epsilon;
}
console.log(areEqual(0.1 + 0.2, 0.3)); // true
// Решение 2: работа с целыми числами
(0.1 * 10 + 0.2 * 10) / 10 === 0.3; // true
// Решение 3: toFixed (осторожно — возвращает строку)
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3; // true
7.2 Погрешность накапливается
javascript
let sum = 0;
for (let i = 0; i < 10; i++) {
sum += 0.1;
}
console.log(sum); // 0.9999999999999999 (не 1)
console.log(sum === 1); // false
7.3 Неожиданные результаты
javascript
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.7); // 0.7999999999999999
console.log(0.2 + 0.4); // 0.6000000000000001
// Есть и точные комбинации
console.log(0.25 + 0.25); // 0.5 (точно)
console.log(0.125 + 0.125); // 0.25 (точно)
Часть 8. Преобразование в число
8.1 Явное преобразование
javascript
Number("123"); // 123
Number("123abc"); // NaN
Number(" 123 "); // 123
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
// Унарный плюс (быстрее)
+"123"; // 123
+true; // 1
// parseInt и parseFloat
parseInt("123px"); // 123
parseInt("1010", 2); // 10 (двоичная)
parseFloat("3.14cm"); // 3.14
// Осторожно с parseInt
parseInt("0x10"); // 16 (шестнадцатеричная)
parseInt("08"); // 8 (но в старых браузерах могло быть 0)
// Всегда указывайте основание:
parseInt("08", 10); // 8
8.2 Неявное преобразование
javascript
"5" - 3; // 2 (минус любит числа)
"5" * "2"; // 10
"5" / "2"; // 2.5
"hello" - 1; // NaN
// Плюс — особенный
"5" + 3; // "53" (строковая конкатенация)
Часть 9. Округление: Тонкости и подводные камни
javascript
const num = 3.14159;
Math.floor(num); // 3 (вниз)
Math.ceil(num); // 4 (вверх)
Math.round(num); // 3 (до ближайшего)
Math.trunc(num); // 3 (отбрасывает дробную часть)
// Проблема с round
Math.round(1.5); // 2
Math.round(-1.5); // -1 (а не -2! округляет к ближайшему целому вверх)
// Форматирование с toFixed
(3.14159).toFixed(2); // "3.14" (строка!)
parseFloat((3.14159).toFixed(2)); // 3.14 (число)
Часть 10. Работа с большими числами
javascript
// Проблема с очень большими числами
const huge = 1e308;
console.log(huge * 2); // Infinity
// Проверка на переполнение
function safeMultiply(a, b) {
const result = a * b;
if (result === Infinity || result === -Infinity) {
throw new Error("Переполнение!");
}
return result;
}
// Решение с BigInt
const big1 = 10n ** 100n; // 10 в степени 100 (огромное число)
const big2 = 123456789012345678901234567890n;
Часть 11. Полезные трюки
11.1 Быстрое округление (побитовые операторы)
javascript
const num = 3.14;
console.log(~~num); // 3 (двойное НЕ)
console.log(num | 0); // 3 (битовое ИЛИ)
console.log(num >> 0); // 3 (сдвиг)
// Внимание: работают только с 32-битными числами!
console.log(~~2147483648); // -2147483648 (переполнение)
11.2 Проверка, является ли число целым
javascript
function isInteger(num) {
return num % 1 === 0;
}
console.log(isInteger(42)); // true
console.log(isInteger(42.5)); // false
console.log(Number.isInteger(42)); // современный способ
11.3 Генерация случайного числа в диапазоне
javascript
function random(min, max) {
return Math.random() * (max - min) + min;
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(random(5, 10)); // 5..10 (дробное)
console.log(randomInt(5, 10)); // 5,6,7,8,9,10 (целое)
11.4 Суммирование с защитой от погрешностей
javascript
function sumSafe(...nums) {
let total = 0;
for (const num of nums) {
total = parseFloat((total + num).toFixed(10));
}
return total;
}
console.log(sumSafe(0.1, 0.2, 0.3)); // 0.6 (не 0.6000000000000001)
Часть 12. Подводные камни (обязательно к прочтению!)
Камень #1: typeof NaN === "number"
javascript
console.log(typeof NaN); // "number" (самый коварный баг)
Камень #2: Math.max() и Math.min() без аргументов
javascript
console.log(Math.max()); // -Infinity
console.log(Math.min()); // Infinity
console.log(Math.max() > Math.min()); // true (да, так бывает)
Камень #3: Сравнение NaN
javascript
const result = 0 / 0;
if (result === NaN) { // никогда не сработает
console.log("NaN");
}
if (Number.isNaN(result)) {
console.log("NaN"); // правильно
}
Камень #4: Потеря точности при больших целых
javascript
console.log(9999999999999999); // 10000000000000000 (округлилось!)
Камень #5: toFixed возвращает строку
javascript
const price = 9.99;
const rounded = price.toFixed(1); // "10.0" (строка!)
const total = rounded + 5; // "10.05" (конкатенация!)
// Всегда преобразуйте обратно: parseFloat(rounded)
Часть 13. Реальные кейсы
13.1 Финансовые расчёты
javascript
// Плохо (с плавающей точкой)
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Хорошо (в копейках)
function calculateTotalSafe(items) {
const totalCents = items.reduce((sum, item) => sum + item.priceCents, 0);
return totalCents / 100;
}
// Или с использованием BigInt для точности
function calculateTotalBigInt(items) {
const totalCents = items.reduce(
(sum, item) => sum + BigInt(item.priceCents),
0n
);
return Number(totalCents) / 100;
}
13.2 Проверка на целое число
javascript
function isIntegerSafe(num) {
return Number.isFinite(num) && Math.floor(num) === num;
}
console.log(isIntegerSafe(42)); // true
console.log(isIntegerSafe(42.5)); // false
console.log(isIntegerSafe(Infinity)); // false
console.log(isIntegerSafe(NaN)); // false
13.3 Сравнение с погрешностью
javascript
function numbersEqual(a, b, tolerance = Number.EPSILON) {
return Math.abs(a - b) < tolerance;
}
console.log(numbersEqual(0.1 + 0.2, 0.3)); // true
Итог: Манифест чисел
- Все числа - 64-битные float - нет отдельного типа для целых.
- 0.1 + 0.2 !== 0.3 - из-за двоичного представления. Используйте погрешность.
- NaN не равен сам себе - проверяйте через Number.isNaN().
- Infinity и -Infinity - результат деления на ноль.
- BigInt - для целых чисел больше 2^53 - 1.
- toFixed возвращает строку - не забывайте преобразовывать обратно.
- Для финансов - работайте в копейках/центах (целых числах) или используйте BigInt.
Финальный тест (проверьте себя):
javascript
console.log(0.1 + 0.2 === 0.3);
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));
console.log(1 / 0);
console.log(parseInt("08"));
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);
console.log(typeof NaN);
console.log(0.1 + 0.7 === 0.8);
Ответы: false, false, true, Infinity, 8 (с указанием основания 10), true (потеря точности), "number", false.
Числа в JavaScript - это одновременно и сила, и слабость языка. С одной стороны, простота (один тип на всё). С другой - необходимость помнить о погрешностях, безопасных диапазонах и специальных значениях. Но когда вы понимаете эти особенности, перестаёте бояться 0.1 + 0.2 и начинаете использовать числа в полной их мощи. Считайте с удовольствием!
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
Искусство управления текстом: Полное руководство по строкам в JavaScript
Строки - это, пожалуй, самый частый гость в вашем коде. Имена пользователей, сообщения в чате, JSON-ответы от сервера, URL-адреса, CSS-классы - всё это строки. Мы работаем с ними каждый день, но часто используем лишь малую часть их возможностей.
Строки в JavaScript - это не просто последовательности символов. Это неизменяемые, умные, богатые методами структуры, которые умеют искать, заменять, резать, склеивать и даже выполнять регулярные выражения.
Сегодня мы погрузимся в мир строк от основ до продвинутых техник.
Часть 1. Что такое строка в JavaScript?
Строка - это последовательность символов (букв, цифр, знаков препинания, эмодзи). В JavaScript строки представлены в кодировке UTF-16, что позволяет работать с любыми символами Юникода.
javascript
const str1 = "Привет, мир!"; // двойные кавычки
const str2 = 'Hello, world!'; // одинарные кавычки
const str3 = `Шаблонная строка`; // обратные кавычки (ES6)
Важно: Строки неизменяемы (immutable). Вы не можете изменить отдельный символ существующей строки.
javascript
let str = "hello";
str[0] = "H";
console.log(str); // "hello" (не изменилось!)
// Чтобы изменить, нужно создать новую строку
str = "H" + str.slice(1);
console.log(str); // "Hello"
Часть 2. Три способа создать строку (и когда какой использовать)
2.1 Одинарные и двойные кавычки - полные близнецы
javascript
const single = 'Одинарные кавычки';
const double = "Двойные кавычки";
// Разницы нет, выбирайте по вкусу
const withDoubleInside = "Он сказал: 'Привет'";
const withSingleInside = 'Она ответила: "Привет"';
// Экранирование
const quote = 'Он сказал: \'Привет\'';
const backslash = "Путь к файлу: C:\\Users\\Name";
2.2 Шаблонные строки (обратные кавычки) - современный стандарт
javascript
const name = "Анна";
const age = 25;
// Интерполяция переменных
const greeting = `Привет, ${name}! Тебе ${age} лет.`;
console.log(greeting); // "Привет, Анна! Тебе 25 лет."
// Многострочность (без \n!)
const multiline = `
Первая строка
Вторая строка
Третья строка
`;
// Выражения внутри ${}
const total = `Сумма: ${10 + 20}`; // "Сумма: 30"
const condition = `Статус: ${age >= 18 ? "взрослый" : "ребёнок"}`;
Часть 3. Свойства и методы строк
3.1 length - длина строки
javascript
const str = "JavaScript";
console.log(str.length); // 10
// Эмодзи считаются как один символ
const emoji = "😊";
console.log(emoji.length); // 1 (в современных движках)
3.2 Доступ к символам
javascript
const str = "Hello";
// Квадратные скобки (только чтение)
console.log(str[0]); // "H"
console.log(str[4]); // "o"
console.log(str[10]); // undefined
// charAt (более старый способ)
console.log(str.charAt(0)); // "H"
console.log(str.charAt(10)); // "" (пустая строка)
// charCodeAt — код символа (UTF-16)
console.log(str.charCodeAt(0)); // 72 (код 'H')
console.log(str.charCodeAt(0).toString(16)); // "48" (шестнадцатеричный)
3.3 Итерация по строке
javascript
const str = "Привет";
// for...of (работает с эмодзи правильно)
for (const char of str) {
console.log(char);
}
// П, р, и, в, е, т
// for (старый способ, ломается на эмодзи)
for (let i = 0; i < str.length; i++) {
console.log(str[i]);
}
Часть 4. Основные методы работы со строками
4.1 Поиск в строке
javascript
const str = "JavaScript - замечательный язык";
// indexOf - индекс первого вхождения
console.log(str.indexOf("замечательный")); // 11
console.log(str.indexOf("Python")); // -1 (не найдено)
// lastIndexOf - индекс последнего вхождения
console.log(str.lastIndexOf("а")); // 18
// includes - проверяет наличие
console.log(str.includes("Java")); // true
console.log(str.includes("Python")); // false
console.log(str.includes("скрипт", 5)); // с позиции 5
// startsWith / endsWith
console.log(str.startsWith("Java")); // true
console.log(str.startsWith("Script")); // false
console.log(str.endsWith("язык")); // true
4.2 Извлечение частей строки
javascript
const str = "JavaScript is awesome";
// slice (поддерживает отрицательные индексы)
console.log(str.slice(0, 10)); // "JavaScript"
console.log(str.slice(-7)); // "awesome"
console.log(str.slice(-7, -1)); // "awesom"
// substring (не поддерживает отрицательные)
console.log(str.substring(0, 10)); // "JavaScript"
console.log(str.substring(-7)); // "JavaScript is awesome" (отрицательное → 0)
// substr (устаревший, не используйте)
console.log(str.substr(4, 6)); // "Script" (с 4, длина 6)
4.3 Регистр символов
javascript
const str = "JavaScript";
console.log(str.toUpperCase()); // "JAVASCRIPT"
console.log(str.toLowerCase()); // "javascript"
// toLocaleUpperCase для специфичных языков
const turkish = "İstanbul";
console.log(turkish.toLocaleLowerCase('tr-TR')); // "istanbul" (без точки над i)
4.4 Обрезка пробелов
javascript
const str = " Hello, World! ";
console.log(str.trim()); // "Hello, World!"
console.log(str.trimStart()); // "Hello, World! "
console.log(str.trimEnd()); // " Hello, World!"
// Частый кейс - очистка ввода пользователя
const userInput = " admin ";
const cleanInput = userInput.trim();
4.5 Замена
javascript
const str = "JavaScript is awesome. JavaScript is powerful.";
// replace - заменяет только первое вхождение
console.log(str.replace("JavaScript", "JS"));
// "JS is awesome. JavaScript is powerful."
// replaceAll - заменяет все вхождения (ES2021)
console.log(str.replaceAll("JavaScript", "JS"));
// "JS is awesome. JS is powerful."
// Замена с регулярным выражением (глобальный флаг g)
console.log(str.replace(/JavaScript/g, "JS"));
4.6 Разделение и соединение
javascript
// split - строка → массив
const str = "apple,banana,orange";
const fruits = str.split(",");
console.log(fruits); // ["apple", "banana", "orange"]
const words = "Hello world from JS".split(" ");
console.log(words); // ["Hello", "world", "from", "JS"]
// split с ограничением
const limited = "a,b,c,d".split(",", 2); // ["a", "b"]
// split по пустой строке
const chars = "abc".split(""); // ["a", "b", "c"]
// join - массив → строка
const joined = fruits.join("; ");
console.log(joined); // "apple; banana; orange"
4.7 Повторение строки
javascript
const str = "ha";
console.log(str.repeat(3)); // "hahaha"
console.log("=".repeat(20)); // "===================="
Часть 5. Сравнение строк
Строки сравниваются посимвольно на основе кодов Unicode.
javascript
console.log("a" < "b"); // true (97 < 98)
console.log("ab" < "ac"); // true ('b' < 'c')
console.log("2" > "10"); // true ('2' > '1')
// Правильное сравнение с учётом локали
console.log("apple".localeCompare("banana")); // -1 (apple < banana)
console.log("banana".localeCompare("apple")); // 1
console.log("apple".localeCompare("apple")); // 0
// С учётом регистра и языков
const str1 = "Straße";
const str2 = "STRASSE";
console.log(str1.localeCompare(str2, 'de', { sensitivity: 'base' })); // 0
Часть 6. Эмодзи и спецсимволы
6.1 Эмодзи в строках
javascript
const smiley = "😊";
console.log(smiley.length); // 1 (в современных движках)
// Некоторые эмодзи составные (например, флаги)
const flag = "🇺🇦";
console.log(flag.length); // 2 (состоит из двух символов)
// Семья (составной эмодзи)
const family = "👨👩👧";
console.log(family.length); // 8 (много символов)
6.2 Экранирование
javascript
// Спецсимволы
const newline = "Первая строка\nВторая строка";
const tab = "Имя:\tАнна";
const backslash = "C:\\Users\\Name";
// Юникод
const heart = "\u2764"; // ❤
const smile = "\u{1F600}"; // 😀 (ES6)
console.log(heart, smile);
// Шестнадцатеричные коды
const copyright = "\xA9"; // ©
const registered = "\xAE"; // ®
Часть 7. Шаблонные строки - продвинутые техники
7.1 Тегированные шаблоны
javascript
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<strong>${values[i]}</strong>` : "");
}, "");
}
const name = "Анна";
const role = "админ";
const html = highlight`Пользователь ${name} с ролью ${role}`;
console.log(html); // "Пользователь <strong>Анна</strong> с ролью <strong>админ</strong>"
7.2 Пользовательское экранирование
javascript
function safe(strings, ...values) {
const escape = (str) => {
return str.replace(/[&<>]/g, (m) => {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
};
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? escape(String(values[i])) : "");
}, "");
}
const userInput = "<script>alert('xss')</script>";
const safeHtml = safe`Пользователь ввёл: ${userInput}`;
console.log(safeHtml); // "Пользователь ввёл: <script>alert('xss')</script>"
7.3 Raw строки (без обработки escape-последовательностей)
javascript
const path = String.raw`C:\Users\Name\file.txt`;
console.log(path); // "C:\\Users\\Name\\file.txt"
const multilineRaw = String.raw`Первая строка\nВторая строка`;
console.log(multilineRaw); // "Первая строка\\nВторая строка" (\n не обработано)
Часть 8. Продвинутые методы (ES2020+)
8.1 matchAll - все совпадения с группами
javascript
const str = "Цены: 100 руб, 250 руб, 30 руб";
const regex = /(\d+)\s+руб/g;
for (const match of str.matchAll(regex)) {
console.log(`Цена: ${match[1]}`);
}
// "Цена: 100"
// "Цена: 250"
// "Цена: 30"
8.2 replaceAll (уже упоминали)
javascript
const str = "a+b+c+d";
console.log(str.replaceAll("+", " and ")); // "a and b and c and d"
8.3 trimStart / trimEnd (ES2019)
javascript
const str = " hello ";
console.log(str.trimStart()); // "hello "
console.log(str.trimEnd()); // " hello"
Часть 9. Преобразование в строку и из строки
9.1 В строку
javascript
// String() - предпочтительный способ
String(123); // "123"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
// toString() - работает не со всеми типами
(123).toString(); // "123"
true.toString(); // "true"
// null.toString(); // TypeError!
// Конкатенация с пустой строкой (быстро, но неявно)
123 + ""; // "123"
true + ""; // "true"
9.2 Из строки
javascript
// В число
Number("123"); // 123
+"123"; // 123
parseInt("123px"); // 123
parseFloat("3.14cm"); // 3.14
// В массив
Array.from("hello"); // ["h","e","l","l","o"]
[...str]; // ["h","e","l","l","o"]
// В объект
JSON.parse('{"name":"Анна"}'); // { name: "Анна" }
Часть 10. Производительность строк
10.1 Конкатенация в циклах
javascript
// Плохо (создаёт много промежуточных строк)
let result = "";
for (let i = 0; i < 10000; i++) {
result += "a";
}
// Хорошо (массив + join)
const parts = [];
for (let i = 0; i < 10000; i++) {
parts.push("a");
}
const result = parts.join("");
10.2 Шаблонные строки vs конкатенация
javascript
const name = "Анна";
const age = 25;
// Шаблонные строки (читаемо, производительность хорошая)
const message1 = `Привет, ${name}! Тебе ${age} лет.`;
// Конкатенация (было быстрее в старых движках, сейчас разница незначительна)
const message2 = "Привет, " + name + "! Тебе " + age + " лет.";
Часть 11. Реальные кейсы
11.1 Валидация email
javascript
function isValidEmail(email) {
const trimmed = email.trim();
if (!trimmed) return false;
const atIndex = trimmed.indexOf('@');
if (atIndex === -1 || atIndex === 0 || atIndex === trimmed.length - 1) {
return false;
}
const dotIndex = trimmed.lastIndexOf('.');
if (dotIndex === -1 || dotIndex < atIndex + 2 || dotIndex === trimmed.length - 1) {
return false;
}
return true;
}
console.log(isValidEmail("user@example.com")); // true
console.log(isValidEmail("user@example")); // false
11.2 Сокращение длинного текста
javascript
function truncate(text, maxLength, suffix = "...") {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength - suffix.length) + suffix;
}
console.log(truncate("Очень длинный текст, который нужно сократить", 20));
// "Очень длинный текст..."
11.3 Капитализация первой буквы
javascript
function capitalize(str) {
if (!str) return str;
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
console.log(capitalize("javaSCRIPT")); // "Javascript"
console.log(capitalize("анна")); // "Анна"
11.4 Склонение слов
javascript
function pluralize(count, forms) {
const remainder10 = count % 10;
const remainder100 = count % 100;
if (remainder100 >= 11 && remainder100 <= 19) {
return forms[2];
}
if (remainder10 === 1) {
return forms[0];
}
if (remainder10 >= 2 && remainder10 <= 4) {
return forms[1];
}
return forms[2];
}
const days = ["день", "дня", "дней"];
console.log(`${1} ${pluralize(1, days)}`); // "1 день"
console.log(`${3} ${pluralize(3, days)}`); // "3 дня"
console.log(`${5} ${pluralize(5, days)}`); // "5 дней"
11.5 Генерация случайной строки
javascript
function randomString(length, chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
let result = "";
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
console.log(randomString(8)); // "aB3xY7kL"
console.log(randomString(10, "0123456789")); // только цифры
11.6 Подсчёт слов
javascript
function countWords(text) {
const words = text.trim().split(/\s+/);
return words[0] === "" ? 0 : words.length;
}
console.log(countWords("Hello world from JavaScript")); // 4
console.log(countWords("")); // 0
Часть 12. Подводные камни
Камень #1: Сравнение строк с разными регистрами
javascript
console.log("Hello" === "hello"); // false
// Решение
console.log("Hello".toLowerCase() === "hello".toLowerCase()); // true
Камень #2: Пустая строка и пробелы
javascript
console.log("" == false); // true (приведение типов)
console.log(" " == false); // true (строка с пробелом → число 0)
// Всегда проверяйте .trim()
if (userInput.trim()) {
// не пустая строка
}
Камень #3: Строковые индексы только для чтения
javascript
let str = "hello";
str[0] = "H";
console.log(str); // "hello" (не изменилось!)
Камень #4: substring и отрицательные индексы
javascript
const str = "hello";
console.log(str.substring(-3)); // "hello" (отрицательное → 0)
console.log(str.slice(-3)); // "llo" (работает с отрицательными)
Часть 13. Полезные трюки
13.1 Обратный порядок символов
javascript
const reverse = str => str.split("").reverse().join("");
console.log(reverse("hello")); // "olleh"
// С эмодзи (осторожно!)
const reverseUnicode = str => [...str].reverse().join("");
console.log(reverseUnicode("hello😊")); // "😊olleh"
13.2 Удаление всех пробелов
javascript
const str = " a b c ";
console.log(str.replace(/\s/g, "")); // "abc"
13.3 Проверка на палиндром
javascript
function isPalindrome(str) {
const cleaned = str.toLowerCase().replace(/[^а-яa-z]/g, "");
const reversed = cleaned.split("").reverse().join("");
return cleaned === reversed;
}
console.log(isPalindrome("А роза упала на лапу Азора")); // true
console.log(isPalindrome("hello")); // false
Итог: Манифест строк
- Строки неизменяемы - любая операция создаёт новую строку.
- Три типа кавычек - одинарные, двойные, обратные (шаблонные).
- Шаблонные строки - интерполяция, многострочность, тегирование.
- Методы не меняют оригинал - всегда возвращают новую строку.
- length - количество символов (осторожно с эмодзи).
- slice - самый гибкий способ извлечения (поддерживает отрицательные индексы).
- trim - всегда чистите пользовательский ввод.
- includes / startsWith / endsWith - современные методы поиска.
- Регулярные выражения - мощь для сложного поиска и замены.
Финальный тест (проверьте себя):
javascript
console.log("hello"[0]);
console.log("hello".charAt(10));
console.log("JavaScript".slice(-4));
console.log(" test ".trim());
console.log("a,b,c".split(",").join("-"));
console.log("Hello" + " " + "World");
console.log(`2 + 2 = ${2 + 2}`);
console.log("JavaScript".replace("a", "o"));
console.log("JavaScript".replaceAll("a", "o"));
console.log("🤔".length);
Ответы: "h", "" (пустая строка), "ript", "test", "a-b-c", "Hello World", "2 + 2 = 4", "JovoScript" (заменило только первое a), "JovoScript" (заменило все a), 1 (или 2 в старых движках).
Строки - это фундамент общения программы с миром. Они хранят имена, сообщения, данные. Умение работать со строками - это умение писать понятный, надёжный и безопасный код. Освойте их методы, и ваш код станет чище, а вы - увереннее. И пусть ваши строки всегда будут правильной длины!
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
Укрощение стихии: Полное руководство по массивам в JavaScript
Массивы в JavaScript - это как швейцарский нож программиста. Они могут быть списком покупок, очередью задач, стеком вызовов, матрицей координат и даже деревом категорий. Они гибкие, динамические и полны сюрпризов.
Но с большой гибкостью приходит большая ответственность. Массивы в JS - это не просто "списки элементов". Это объекты с числовыми ключами, специальным свойством length и целой армией методов, которые могут менять, фильтровать, преобразовывать и агрегировать данные.
Сегодня мы разберём массивы от А до Я: от создания до продвинутых техник работы.
Часть 1. Что такое массив?
Массив - это упорядоченная коллекция элементов. В JavaScript массивы — это особый тип объектов с числовыми ключами (индексами) и специальным свойством length.
javascript
const fruits = ["яблоко", "банан", "апельсин"];
console.log(fruits[0]); // "яблоко"
console.log(fruits[1]); // "банан"
console.log(fruits.length); // 3
Важное отличие от других языков: в JavaScript массивы могут содержать элементы разных типов.
javascript
const mixed = [42, "строка", true, null, { name: "Анна" }, [1, 2, 3]];
console.log(mixed[0]); // 42
console.log(mixed[4]); // { name: "Анна" }
console.log(mixed[5]); // [1, 2, 3]
Часть 2. Создание массивов
2.1 Литерал массива [] - самый простой и быстрый
javascript
const empty = []; // пустой массив
const numbers = [1, 2, 3]; // с числами
const mixed = [1, "two", true]; // смешанный
const nested = [[1, 2], [3, 4]]; // вложенный
2.2 Конструктор Array()
javascript
// Пустой массив
const arr1 = new Array();
// Массив с заданной длиной (создаёт пустые "дырки")
const arr2 = new Array(5);
console.log(arr2.length); // 5
console.log(arr2[0]); // undefined (но это не то же самое, что пустой элемент)
// Массив с элементами
const arr3 = new Array(1, 2, 3);
console.log(arr3); // [1, 2, 3]
// Осторожно: один числовой аргумент → длина, а не элемент!
const arr4 = new Array(5); // массив длины 5
const arr5 = new Array("5"); // массив с одним элементом "5"
2.3 Array.of() - решение проблемы конструктора
javascript
console.log(Array.of(5)); // [5] (не массив длины 5!)
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
2.4 Array.from() - из итерируемых объектов
javascript
// Из строки
console.log(Array.from("hello")); // ["h", "e", "l", "l", "o"]
// Из Set
const set = new Set([1, 2, 3]);
console.log(Array.from(set)); // [1, 2, 3]
// Из Map
const map = new Map([["a", 1], ["b", 2]]);
console.log(Array.from(map)); // [["a", 1], ["b", 2]]
// С функцией преобразования
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]
// Создание последовательности
console.log(Array.from({ length: 5 }, (_, i) => i)); // [0, 1, 2, 3, 4]
Часть 3. Свойство length - друг или враг?
length - это не просто количество элементов. Его можно изменять.
javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.length); // 5
// Уменьшение длины - обрезает массив
arr.length = 3;
console.log(arr); // [1, 2, 3]
// Увеличение длины - добавляет пустые "дырки"
arr.length = 5;
console.log(arr); // [1, 2, 3, empty × 2]
console.log(arr[3]); // undefined (но ключа 3 нет)
// Очистка массива
arr.length = 0;
console.log(arr); // []
Важно: length - это наибольший целочисленный индекс + 1.
javascript
const arr = [];
arr[10] = "десять";
console.log(arr.length); // 11 (не 1!)
console.log(arr); // [empty × 10, "десять"]
Часть 4. Основные операции с массивами
4.1 Добавление и удаление элементов
javascript
const arr = [1, 2, 3];
// В конец
arr.push(4); // [1, 2, 3, 4] (возвращает новую длину)
arr.push(5, 6); // [1, 2, 3, 4, 5, 6]
// Из конца
const last = arr.pop(); // 6, arr = [1, 2, 3, 4, 5]
// В начало
arr.unshift(0); // [0, 1, 2, 3, 4, 5] (возвращает новую длину)
// Из начала
const first = arr.shift(); // 0, arr = [1, 2, 3, 4, 5]
4.2 Удаление и вставка в середине (splice)
splice(start, deleteCount, ...items) - самый мощный метод.
javascript
const arr = [1, 2, 3, 4, 5];
// Удаление
const deleted = arr.splice(2, 2); // удаляет 2 элемента с индекса 2
console.log(deleted); // [3, 4]
console.log(arr); // [1, 2, 5]
// Вставка
arr.splice(2, 0, 3, 4); // на место 2, удалить 0, вставить 3 и 4
console.log(arr); // [1, 2, 3, 4, 5]
// Замена
arr.splice(1, 2, 20, 30); // заменить 2 элемента с индекса 1
console.log(arr); // [1, 20, 30, 4, 5]
4.3 Извлечение части (slice)
javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.slice(2)); // [3, 4, 5] (с индекса 2 до конца)
console.log(arr.slice(1, 4)); // [2, 3, 4] (с 1 до 4, не включая 4)
console.log(arr.slice(-2)); // [4, 5] (отрицательные индексы)
console.log(arr.slice()); // [1, 2, 3, 4, 5] (копия массива)
Часть 5. Итерация по массиву
5.1 Классический for
javascript
const arr = ["a", "b", "c"];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
5.2 for...of (для значений, не индексов)
javascript
for (const value of arr) {
console.log(value);
}
5.3 forEach - функциональный подход
javascript
arr.forEach((value, index, array) => {
console.log(`${index}: ${value}`);
});
5.4 Сравнение методов итерации
javascript
const arr = ["a", "b", "c"];
// forEach - просто итерация
arr.forEach(v => console.log(v));
// map - создаёт новый массив
const upper = arr.map(v => v.toUpperCase()); // ["A", "B", "C"]
// filter - фильтрация
const long = arr.filter(v => v.length > 1);
// reduce - агрегация
const sum = [1, 2, 3].reduce((acc, v) => acc + v, 0); // 6
// some / every - проверка
const hasA = arr.some(v => v === "a"); // true
const allA = arr.every(v => v === "a"); // false
// find / findIndex - поиск
const found = arr.find(v => v === "b"); // "b"
const index = arr.findIndex(v => v === "b"); // 1
Часть 6. Методы преобразования
6.1 map - трансформация
javascript
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// С доступом к индексу
const indexed = numbers.map((n, i) => `${i}: ${n}`);
// ["0: 1", "1: 2", "2: 3", "3: 4", "4: 5"]
6.2 filter - отбор
javascript
const numbers = [1, 2, 3, 4, 5, 6];
const even = numbers.filter(n => n % 2 === 0);
console.log(even); // [2, 4, 6]
// Фильтрация объектов
const users = [
{ name: "Анна", age: 25 },
{ name: "Борис", age: 17 },
{ name: "Вика", age: 30 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults); // [{ name: "Анна", age: 25 }, { name: "Вика", age: 30 }]
6.3 reduce - свертка (самый мощный)
javascript
const numbers = [1, 2, 3, 4, 5];
// Сумма
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
// Произведение
const product = numbers.reduce((acc, n) => acc * n, 1); // 120
// Максимум
const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); // 5
// Группировка
const fruits = ["яблоко", "банан", "яблоко", "апельсин", "банан", "банан"];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // { яблоко: 2, банан: 3, апельсин: 1 }
6.4 flat и flatMap - работа с вложенностью
javascript
// flat - разворачивание
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // полностью развернуть
// flatMap - map + flat (глубина 1)
const sentences = ["Hello world", "JavaScript is awesome"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["Hello", "world", "JavaScript", "is", "awesome"]
Часть 7. Поиск в массиве
javascript
const arr = [1, 2, 3, 4, 5, 2];
// indexOf - индекс первого вхождения
console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(2, 2)); // 5 (поиск с индекса 2)
console.log(arr.indexOf(10)); // -1
// lastIndexOf - индекс последнего вхождения
console.log(arr.lastIndexOf(2)); // 5
// includes - проверка наличия
console.log(arr.includes(3)); // true
console.log(arr.includes(10)); // false
// find - поиск элемента по условию
const found = arr.find(n => n > 3);
console.log(found); // 4
// findIndex - поиск индекса по условию
const idx = arr.findIndex(n => n > 3);
console.log(idx); // 3
// findLast / findLastIndex (ES2023) - поиск с конца
const lastFound = arr.findLast(n => n > 3);
console.log(lastFound); // 5
Часть 8. Сортировка и порядок
8.1 sort() - коварный метод
javascript
const numbers = [1, 30, 4, 21, 100000];
// Без функции сравнения (сортирует как строки!)
numbers.sort();
console.log(numbers); // [1, 100000, 21, 30, 4] (по строкам!)
// С функцией сравнения
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 4, 21, 30, 100000] (по возрастанию)
// По убыванию
numbers.sort((a, b) => b - a);
console.log(numbers); // [100000, 30, 21, 4, 1]
Важно: sort() изменяет исходный массив.
javascript
const arr = [3, 1, 2];
const sorted = arr.sort();
console.log(arr === sorted); // true (один и тот же массив)
8.2 reverse() - обратный порядок
javascript
const arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1] (изменяет исходный)
8.3 toSorted() и toReversed() (ES2023) - без мутации
javascript
const arr = [3, 1, 2];
const sorted = arr.toSorted(); // [1, 2, 3]
const reversed = arr.toReversed(); // [2, 1, 3]
console.log(arr); // [3, 1, 2] (не изменился)
Часть 9. Специальные методы
9.1 join() - склеивание в строку
javascript
const fruits = ["яблоко", "банан", "апельсин"];
console.log(fruits.join()); // "яблоко,банан,апельсин"
console.log(fruits.join(", ")); // "яблоко, банан, апельсин"
console.log(fruits.join(" и ")); // "яблоко и банан и апельсин"
console.log(fruits.join("")); // "яблокобананапельсин"
9.2 concat() - объединение массивов
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combined = arr1.concat(arr2, arr3);
console.log(combined); // [1, 2, 3, 4, 5, 6]
// Современный способ (spread)
const combined2 = [...arr1, ...arr2, ...arr3];
9.3 fill() - заполнение
javascript
const arr = new Array(5);
arr.fill(0);
console.log(arr); // [0, 0, 0, 0, 0]
arr.fill(1, 2, 4); // с индекса 2 до 4 (не включая)
console.log(arr); // [0, 0, 1, 1, 0]
9.4 copyWithin() - копирование внутри массива
javascript
const arr = [1, 2, 3, 4, 5];
arr.copyWithin(0, 3, 5); // скопировать элементы с 3 по 5 на позицию 0
console.log(arr); // [4, 5, 3, 4, 5]
Часть 10. Проверка на массив
javascript
const arr = [1, 2, 3];
const obj = {};
console.log(typeof arr); // "object" (не поможет!)
// Array.isArray - правильный способ
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false
// instanceof (работает, но может ошибаться между фреймами)
console.log(arr instanceof Array); // true
Часть 11. Деструктуризация массивов
javascript
const numbers = [1, 2, 3, 4, 5];
// Базовая
const [a, b, c] = numbers;
console.log(a, b, c); // 1, 2, 3
// Пропуск элементов
const [first, , third] = numbers;
console.log(first, third); // 1, 3
// Остаточные элементы (rest)
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// Значения по умолчанию
const [x = 0, y = 0] = [];
console.log(x, y); // 0, 0
// Обмен переменных
let i = 1, j = 2;
[i, j] = [j, i];
console.log(i, j); // 2, 1
Часть 12. Массив как стек и очередь
Стек (LIFO - последним пришёл, первым ушёл)
javascript
const stack = [];
stack.push(1, 2, 3); // добавляем
console.log(stack); // [1, 2, 3]
const top = stack.pop(); // извлекаем
console.log(top); // 3
console.log(stack); // [1, 2]
Очередь (FIFO - первым пришёл, первым ушёл)
javascript
const queue = [];
queue.push(1, 2, 3); // добавляем в конец
console.log(queue); // [1, 2, 3]
const front = queue.shift(); // извлекаем из начала
console.log(front); // 1
console.log(queue); // [2, 3]
Часть 13. Реальные паттерны
13.1 Уникальные значения
javascript
const numbers = [1, 2, 2, 3, 3, 4, 5, 5];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]
13.2 Пересечение массивов
javascript
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 5, 6];
const intersection = arr1.filter(x => arr2.includes(x));
console.log(intersection); // [3, 4]
13.3 Разность массивов
javascript
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 5, 6];
const difference = arr1.filter(x => !arr2.includes(x));
console.log(difference); // [1, 2]
13.4 Группировка по свойству
javascript
const users = [
{ name: "Анна", city: "Москва" },
{ name: "Борис", city: "СПб" },
{ name: "Вика", city: "Москва" },
{ name: "Глеб", city: "СПб" }
];
const byCity = users.reduce((acc, user) => {
if (!acc[user.city]) acc[user.city] = [];
acc[user.city].push(user);
return acc;
}, {});
console.log(byCity);
// {
// Москва: [{ name: "Анна", city: "Москва" }, { name: "Вика", city: "Москва" }],
// СПб: [{ name: "Борис", city: "СПб" }, { name: "Глеб", city: "СПб" }]
// }
13.5 Плоский массив (любой глубины)
javascript
function flattenDeep(arr) {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flattenDeep(val) : val);
}, []);
}
const nested = [1, [2, [3, [4, 5]]]];
console.log(flattenDeep(nested)); // [1, 2, 3, 4, 5]
13.6 Разбивка на чанки
javascript
function chunk(array, size) {
return array.reduce((acc, _, i) => {
if (i % size === 0) acc.push(array.slice(i, i + size));
return acc;
}, []);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(chunk(numbers, 3)); // [[1,2,3], [4,5,6], [7,8]]
13.7 Очередь с ограничением (LRU Cache)
javascript
class LRUCache {
constructor(limit = 5) {
this.limit = limit;
this.cache = [];
}
add(item) {
// Удаляем, если уже есть
const index = this.cache.indexOf(item);
if (index !== -1) this.cache.splice(index, 1);
// Добавляем в конец
this.cache.push(item);
// Обрезаем, если превышен лимит
if (this.cache.length > this.limit) {
this.cache.shift();
}
}
get() {
return [...this.cache];
}
}
const cache = new LRUCache(3);
cache.add(1); cache.add(2); cache.add(3); cache.add(4);
console.log(cache.get()); // [2, 3, 4]
cache.add(2);
console.log(cache.get()); // [3, 4, 2]
Часть 14. Подводные камни
Камень #1: delete не удаляет элемент правильно
javascript
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, empty, 3]
console.log(arr.length); // 3 (длина не изменилась!)
// Правильно: splice
arr.splice(1, 1);
console.log(arr); // [1, 3]
Камень #2: Массивы - это объекты
javascript
const arr = [1, 2, 3];
arr.customProp = "hello";
console.log(arr.customProp); // "hello"
console.log(arr.length); // 3 (не изменилась)
Камень #3: for...in для массивов - плохо
javascript
const arr = [1, 2, 3];
arr.customProp = "hello";
for (let key in arr) {
console.log(key); // "0", "1", "2", "customProp" (включает свойства!)
}
for (let value of arr) {
console.log(value); // 1, 2, 3 (только элементы)
}
Камень #4: Сравнение массивов
javascript
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (разные объекты)
// Сравнение содержимого
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((val, i) => val === b[i]);
}
console.log(arraysEqual(arr1, arr2)); // true
Итог: Манифест массивов
- Массивы - это объекты с числовыми ключами и свойством length.
- length можно изменять - обрезать и расширять массив.
- Методы делятся на мутирующие и немутирующие:
Мутируют: push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin
Не мутируют: slice, concat, map, filter, reduce, forEach, find, includes - sort() без функции сравнения - сортирует как строки (коварно!).
- Array.isArray() - единственный надёжный способ проверки.
- Деструктуризация - удобно для обмена переменных и извлечения элементов.
- Spread (...) - современная альтернатива concat().
Финальный тест (проверьте себя):
javascript
const arr = [1, 2, 3];
arr[10] = 10;
console.log(arr.length);
const arr2 = [1, 2, 3];
const result = arr2.map(x => x * 2);
console.log(arr2);
const arr3 = [1, 2, 3, 4, 5];
arr3.splice(2, 2);
console.log(arr3);
const arr4 = [1, 2, 3];
const [a, , c] = arr4;
console.log(a, c);
Ответы: 11, [1, 2, 3] (map не изменяет оригинал), [1, 2, 5], 1, 3.
Массивы - это рабочие лошадки JavaScript. Они везде: в обработке данных, в работе с DOM, в API, в состоянии приложений. Освойте их методы, и вы сможете выражать сложные операции в одной строке кода. Помните: хороший код - это не тот, который работает, а тот, который понятен. Используйте массивы с умом, и ваши коллеги скажут вам спасибо.
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
Армия методов: Как массивы JavaScript стали самыми мощными инструментами в истории языка
Массивы в JavaScript - это не просто "списки элементов". Это настоящий швейцарский арсенал. Если вы всё ещё используете for (let i = 0; i < arr.length; i++) для всего подряд - вы тратите свою жизнь впустую.
Методы массивов - это декларативная революция. Вместо того чтобы объяснять компьютеру как итерировать, вы говорите ему что сделать. map для трансформации, filter для отбора, reduce для агрегации. Это не просто удобно - это меняет способ мышления о данных.
Сегодня мы пройдём по всем методам массивов: от священной троицы (map/filter/reduce) до экзотических flatMap, with и группировки.
Часть 1. Почему методы массивов лучше циклов?
javascript
// Старый мир (императивный)
const numbers = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
// Новый мир (декларативный)
const doubled = numbers.map(n => n * 2);
Разница:
- Короче - одна строка вместо пяти
- Читаемее - сразу понятно, что происходит
- Без побочных эффектов - не мутирует исходный массив
- Легче комбинировать - цепочки методов
Часть 2. Священная троица: map, filter, reduce
2.1 map - трансформация каждого элемента
map создаёт новый массив, применяя функцию к каждому элементу.
javascript
const numbers = [1, 2, 3, 4, 5];
// Удвоение
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Извлечение свойств
const users = [
{ name: "Анна", age: 25 },
{ name: "Борис", age: 30 },
{ name: "Вика", age: 28 }
];
const names = users.map(user => user.name);
console.log(names); // ["Анна", "Борис", "Вика"]
// С доступом к индексу
const indexed = numbers.map((n, i) => `${i}: ${n}`);
console.log(indexed); // ["0: 1", "1: 2", "2: 3", "3: 4", "4: 5"]
// С доступом к исходному массиву
const withPrev = numbers.map((n, i, arr) => n + (arr[i - 1] || 0));
console.log(withPrev); // [1, 3, 5, 7, 9]
2.2 filter - отбор элементов
filter создаёт новый массив с элементами, прошедшими проверку.
javascript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Чётные числа
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// Удаление null/undefined
const withNulls = [1, null, 2, undefined, 3, null, 4];
const clean = withNulls.filter(item => item != null);
console.log(clean); // [1, 2, 3, 4]
// Фильтрация объектов
const adults = users.filter(user => user.age >= 18);
console.log(adults); // все пользователи (все >=18)
// Уникальные значения (простой способ)
const duplicates = [1, 2, 2, 3, 3, 4, 5, 5];
const unique = duplicates.filter((value, index, arr) => arr.indexOf(value) === index);
console.log(unique); // [1, 2, 3, 4, 5]
2.3 reduce - агрегация всего и вся
reduce - самый мощный метод. Он свёртывает массив в одно значение.
javascript
const numbers = [1, 2, 3, 4, 5];
// Сумма
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// Произведение
const product = numbers.reduce((acc, n) => acc * n, 1);
console.log(product); // 120
// Максимум
const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity);
console.log(max); // 5
// Объект-счётчик
const fruits = ["яблоко", "банан", "яблоко", "апельсин", "банан", "банан"];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // { яблоко: 2, банан: 3, апельсин: 1 }
// Группировка по свойству
const grouped = users.reduce((acc, user) => {
const firstLetter = user.name[0];
if (!acc[firstLetter]) acc[firstLetter] = [];
acc[firstLetter].push(user);
return acc;
}, {});
// { А: [{ name: "Анна", age: 25 }], Б: [...], В: [...] }
// Плоский массив из вложенного
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Цепочка функций
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const result = pipe(double, addOne, square)(5); // ((5*2)+1)^2 = 121
Часть 3. Методы поиска
3.1 find и findIndex - первый подходящий
javascript
const users = [
{ id: 1, name: "Анна" },
{ id: 2, name: "Борис" },
{ id: 3, name: "Вика" },
{ id: 4, name: "Анна" }
];
// Найти первого пользователя с именем "Анна"
const anna = users.find(user => user.name === "Анна");
console.log(anna); // { id: 1, name: "Анна" }
// Найти индекс
const index = users.findIndex(user => user.name === "Анна");
console.log(index); // 0
// Когда ничего не найдено
const none = users.find(user => user.name === "Глеб");
console.log(none); // undefined
const noneIndex = users.findIndex(user => user.name === "Глеб");
console.log(noneIndex); // -1
3.2 findLast и findLastIndex (ES2023) — с конца
javascript
const users = [
{ id: 1, name: "Анна" },
{ id: 2, name: "Борис" },
{ id: 3, name: "Вика" },
{ id: 4, name: "Анна" }
];
// Найти последнего пользователя с именем "Анна"
const lastAnna = users.findLast(user => user.name === "Анна");
console.log(lastAnna); // { id: 4, name: "Анна" }
const lastIndex = users.findLastIndex(user => user.name === "Анна");
console.log(lastIndex); // 3
3.3 indexOf и lastIndexOf - для примитивов
javascript
const numbers = [1, 2, 3, 4, 5, 2];
console.log(numbers.indexOf(2)); // 1
console.log(numbers.indexOf(2, 2)); // 5 (поиск с индекса 2)
console.log(numbers.indexOf(10)); // -1
console.log(numbers.lastIndexOf(2)); // 5
3.4 includes - проверка наличия
javascript
const fruits = ["яблоко", "банан", "апельсин"];
console.log(fruits.includes("банан")); // true
console.log(fruits.includes("груша")); // false
console.log(fruits.includes("банан", 2)); // false (поиск с индекса 2)
3.5 some и every - проверка условия
javascript
const numbers = [1, 2, 3, 4, 5];
// Хотя бы один чётный?
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true
// Все чётные?
const allEven = numbers.every(n => n % 2 === 0);
console.log(allEven); // false
// Полезно для валидации
const users = [
{ name: "Анна", age: 25 },
{ name: "Борис", age: 17 },
{ name: "Вика", age: 30 }
];
const allAdults = users.every(user => user.age >= 18);
console.log(allAdults); // false
Часть 4. Методы итерации без трансформации
4.1 forEach - просто выполнить
javascript
const numbers = [1, 2, 3, 4, 5];
// Вывод в консоль
numbers.forEach(n => console.log(n));
// С доступом к индексу
numbers.forEach((n, i) => console.log(`[${i}]: ${n}`));
// Внешний побочный эффект
let sum = 0;
numbers.forEach(n => sum += n);
console.log(sum); // 15
// Внимание: нельзя break или return (как в цикле)
numbers.forEach(n => {
if (n === 3) return; // только выход из текущей итерации, не из всего цикла
console.log(n); // 1, 2, 4, 5
});
4.2 map vs forEach - когда что использовать
javascript
// map - когда нужен НОВЫЙ массив
const doubled = numbers.map(n => n * 2);
// forEach - когда нужен побочный эффект
numbers.forEach(n => console.log(n));
// Никогда не используйте map для побочных эффектов
// Плохо: создаёт ненужный массив
numbers.map(n => console.log(n));
Часть 5. Методы изменения структуры
5.1 flat - разворачивание вложенности
javascript
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]] (глубина 1)
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6] (глубина 2)
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6] (вся глубина)
5.2 flatMap - map + flat (глубина 1)
javascript
// Разбить предложения на слова
const sentences = ["Hello world", "JavaScript is awesome"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["Hello", "world", "JavaScript", "is", "awesome"]
// Фильтрация и маппинг за один проход
const numbers = [1, 2, 3, 4, 5];
const doubledEvens = numbers.flatMap(n =>
n % 2 === 0 ? [n * 2] : []
);
console.log(doubledEvens); // [4, 8] (только чётные, удвоенные)
// Альтернатива filter + map (два прохода)
const doubledEvensOld = numbers.filter(n => n % 2 === 0).map(n => n * 2);
5.3 concat - объединение массивов
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combined = arr1.concat(arr2, arr3);
console.log(combined); // [1, 2, 3, 4, 5, 6]
// Spread-альтернатива
const combinedSpread = [...arr1, ...arr2, ...arr3];
5.4 slice - извлечение части (без мутации)
javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.slice(2)); // [3, 4, 5]
console.log(arr.slice(1, 4)); // [2, 3, 4]
console.log(arr.slice(-2)); // [4, 5]
console.log(arr.slice()); // [1, 2, 3, 4, 5] (копия)
5.5 splice - изменение середины (с мутацией)
javascript
const arr = [1, 2, 3, 4, 5];
// Удаление
const deleted = arr.splice(2, 2);
console.log(deleted); // [3, 4]
console.log(arr); // [1, 2, 5]
// Вставка
arr.splice(2, 0, 3, 4);
console.log(arr); // [1, 2, 3, 4, 5]
// Замена
arr.splice(1, 2, 20, 30);
console.log(arr); // [1, 20, 30, 4, 5]
Часть 6. Методы порядка и сортировки
6.1 sort - сортировка (мутирует!)
javascript
const numbers = [1, 30, 4, 21, 100000];
// Без функции - как строки
numbers.sort();
console.log(numbers); // [1, 100000, 21, 30, 4] (НЕ ТАК!)
// По возрастанию
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 4, 21, 30, 100000]
// По убыванию
numbers.sort((a, b) => b - a);
console.log(numbers); // [100000, 30, 21, 4, 1]
// Сортировка строк (с учётом локали)
const words = ["réservé", "premier", "cliché", "communiqué"];
words.sort((a, b) => a.localeCompare(b));
console.log(words); // ["cliché", "communiqué", "premier", "réservé"]
// Сортировка объектов
const users = [
{ name: "Анна", age: 30 },
{ name: "Борис", age: 25 },
{ name: "Вика", age: 35 }
];
users.sort((a, b) => a.age - b.age);
console.log(users); // Борис (25), Анна (30), Вика (35)
6.2 reverse - обратный порядок (мутирует!)
javascript
const arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1]
6.3 toSorted и toReversed (ES2023) - без мутации
javascript
const arr = [3, 1, 4, 1, 5];
const sorted = arr.toSorted(); // [1, 1, 3, 4, 5]
const reversed = arr.toReversed(); // [5, 1, 4, 1, 3]
console.log(arr); // [3, 1, 4, 1, 5] (не изменился!)
// С функцией сравнения
const sortedDesc = arr.toSorted((a, b) => b - a); // [5, 4, 3, 1, 1]
Часть 7. Методы заполнения
7.1 fill - заполнить значением
javascript
const arr = new Array(5);
arr.fill(0);
console.log(arr); // [0, 0, 0, 0, 0]
arr.fill(1, 2, 4); // с индекса 2 до 4 (не включая)
console.log(arr); // [0, 0, 1, 1, 0]
// Создание последовательности
const sequence = new Array(5).fill().map((_, i) => i);
console.log(sequence); // [0, 1, 2, 3, 4]
7.2 copyWithin - копирование внутри массива
javascript
const arr = [1, 2, 3, 4, 5];
arr.copyWithin(0, 3, 5);
console.log(arr); // [4, 5, 3, 4, 5]
// Пример: циклический сдвиг
const shiftLeft = (arr, n) => {
const copy = [...arr];
copy.copyWithin(0, n);
copy.copyWithin(copy.length - n, 0, n);
return copy;
};
console.log(shiftLeft([1, 2, 3, 4, 5], 2)); // [3, 4, 5, 1, 2]
Часть 8. Методы преобразования в строку
8.1 join - склеить в строку
javascript
const fruits = ["яблоко", "банан", "апельсин"];
console.log(fruits.join()); // "яблоко,банан,апельсин"
console.log(fruits.join(", ")); // "яблоко, банан, апельсин"
console.log(fruits.join(" и ")); // "яблоко и банан и апельсин"
console.log(fruits.join("")); // "яблокобананапельсин"
console.log([].join(", ")); // "" (пустая строка)
8.2 toString - то же, что join(',')
javascript
const arr = [1, 2, 3];
console.log(arr.toString()); // "1,2,3"
console.log(String(arr)); // "1,2,3"
Часть 9. Новые методы ES2023
9.1 toSpliced - splice без мутации
javascript
const arr = [1, 2, 3, 4, 5];
const newArr = arr.toSpliced(2, 2, 100, 200);
console.log(newArr); // [1, 2, 100, 200, 5]
console.log(arr); // [1, 2, 3, 4, 5] (не изменился)
9.2 with - замена элемента по индексу
javascript
const arr = [1, 2, 3, 4, 5];
const newArr = arr.with(2, 100);
console.log(newArr); // [1, 2, 100, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5] (не изменился)
Часть 10. Комбинации и цепочки
Сила методов массивов раскрывается в цепочках.
javascript
const users = [
{ name: "Анна", age: 25, city: "Москва", active: true },
{ name: "Борис", age: 17, city: "СПб", active: false },
{ name: "Вика", age: 30, city: "Москва", active: true },
{ name: "Глеб", age: 22, city: "Казань", active: true },
{ name: "Дима", age: 19, city: "Москва", active: false }
];
// Активные совершеннолетние пользователи из Москвы, отсортированные по возрасту
const result = users
.filter(user => user.active) // только активные
.filter(user => user.age >= 18) // совершеннолетние
.filter(user => user.city === "Москва") // из Москвы
.sort((a, b) => a.age - b.age) // по возрасту
.map(user => `${user.name} (${user.age} лет)`); // форматирование
console.log(result); // ["Анна (25 лет)", "Вика (30 лет)"]
// Статистика в одном выражении
const stats = {
total: users.length,
active: users.filter(u => u.active).length,
averageAge: users.reduce((sum, u) => sum + u.age, 0) / users.length,
cities: [...new Set(users.map(u => u.city))]
};
Часть 11. Производительность и оптимизации
javascript
// Плохо: несколько проходов
const result = arr
.filter(x => x > 10)
.map(x => x * 2)
.filter(x => x < 100);
// Хорошо: один проход (reduce)
const result = arr.reduce((acc, x) => {
if (x > 10) {
const doubled = x * 2;
if (doubled < 100) acc.push(doubled);
}
return acc;
}, []);
// Или flatMap
const result = arr.flatMap(x => {
if (x > 10) {
const doubled = x * 2;
return doubled < 100 ? [doubled] : [];
}
return [];
});
Часть 12. Полифиллы для понимания
javascript
// Реализация map
function myMap(arr, callback) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i], i, arr));
}
return result;
}
// Реализация filter
function myFilter(arr, callback) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}
// Реализация reduce
function myReduce(arr, callback, initialValue) {
let accumulator = initialValue;
let startIndex = 0;
if (accumulator === undefined) {
accumulator = arr[0];
startIndex = 1;
}
for (let i = startIndex; i < arr.length; i++) {
accumulator = callback(accumulator, arr[i], i, arr);
}
return accumulator;
}
Итог: Шпаргалка методов
Финальный тест (что выведет?):
javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.map(n => n * 2));
console.log(arr.filter(n => n % 2 === 0));
console.log(arr.reduce((a, b) => a + b, 0));
console.log(arr.find(n => n > 3));
console.log(arr.some(n => n > 10));
console.log(arr.every(n => n > 0));
console.log(arr.slice(1, 4));
console.log(arr.sort((a, b) => b - a));
Ответы: [2,4,6,8,10], [2,4], 15, 4, false, true, [2,3,4], [5,4,3,2,1] (массив изменился).
Методы массивов - это не просто инструменты. Это язык, на котором вы говорите с данными. Чем лучше вы его знаете, тем элегантнее становятся ваши решения. Изучите их, практикуйтесь, комбинируйте. И однажды вы поймёте, что циклы вам больше почти не нужны. А это - настоящая свобода.
------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
Путешественники вселенной: Итераторы, перебираемые объекты и магия for...of
Представьте, что вы можете пройтись по любой структуре данных одним и тем же способом. По массиву - for...of. По строке - for...of. По множеству, словарю, генератору - снова for...of. Даже по своей собственной, с нуля написанной структуре данных.
Это не магия. Это протокол итерации - один из самых элегантных механизмов в JavaScript.
Перебираемые объекты (iterables) - это стандартный интерфейс, который позволяет любому объекту стать "перебираемым". А итераторы - это механизм, который управляет этим перебором.
Сегодня мы разберём, как устроена эта механика, как создавать свои итерируемые объекты и как использовать мощь итераторов для ленивых вычислений.
Часть 1. Что такое перебираемый объект?
Перебираемый объект (iterable) - это любой объект, который умеет возвращать итератор. Встроенные перебираемые объекты: массивы, строки, Map, Set, arguments, DOM-коллекции (NodeList).
javascript
// Массив - перебираемый
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item); // 1, 2, 3
}
// Строка - перебираемая
const str = "hello";
for (const char of str) {
console.log(char); // h, e, l, l, o
}
// Set - перебираемый
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
// Map - перебираемый (возвращает пары [key, value])
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
Часть 2. for...of - король перебора
for...of - это современный способ перебора перебираемых объектов.
javascript
const arr = [10, 20, 30];
// for...of перебирает значения
for (const value of arr) {
console.log(value); // 10, 20, 30
}
// В отличие от for...in, который перебирает ключи
for (const key in arr) {
console.log(key); // "0", "1", "2"
}
// for...of работает с любым перебираемым объектом
const str = "abc";
for (const char of str) {
console.log(char); // "a", "b", "c"
}
Часть 3. Что такое итератор?
Итератор (iterator) - это объект с методом next(), который возвращает объект { value, done }.
javascript
// Создадим простой итератор
const simpleIterator = {
current: 0,
last: 3,
next() {
if (this.current <= this.last) {
return { value: this.current++, done: false };
}
return { value: undefined, done: true };
}
};
console.log(simpleIterator.next()); // { value: 0, done: false }
console.log(simpleIterator.next()); // { value: 1, done: false }
console.log(simpleIterator.next()); // { value: 2, done: false }
console.log(simpleIterator.next()); // { value: 3, done: false }
console.log(simpleIterator.next()); // { value: undefined, done: true }
Часть 4. Протокол итерации
Протокол итерации состоит из двух частей:
- Iterable - объект, у которого есть метод [Symbol.iterator](), возвращающий итератор.
- Iterator - объект с методом next(), возвращающим { value, done }.
javascript
// Объект, реализующий протокол итерации
const range = {
from: 1,
to: 5,
// Метод, делающий объект перебираемым
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
// Возвращаем итератор
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
// Теперь range можно перебирать через for...of!
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// И через spread
const numbers = [...range];
console.log(numbers); // [1, 2, 3, 4, 5]
Часть 5. Встроенные перебираемые объекты
5.1 Массивы
javascript
const arr = [1, 2, 3];
// Получение итератора
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
5.2 Строки (работают с эмодзи правильно!)
javascript
const str = "hello😊";
for (const char of str) {
console.log(char); // h, e, l, l, o, 😊
}
// Разница с for...in
for (const key in str) {
console.log(key); // 0, 1, 2, 3, 4, 5 (индексы, не символы!)
}
5.3 Map (возвращает пары)
javascript
const map = new Map([
["name", "Анна"],
["age", 25]
]);
for (const [key, value] of map) {
console.log(key, value); // "name" "Анна", "age" 25
}
// Можно получить итератор отдельно
const keys = map.keys(); // итератор ключей
const values = map.values(); // итератор значений
const entries = map.entries(); // итератор пар (то же, что map[Symbol.iterator]())
5.4 Set (возвращает значения)
javascript
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value); // 1, 2, 3
}
5.5 arguments (псевдомассив)
javascript
function sum() {
let total = 0;
for (const num of arguments) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3, 4)); // 10
5.6 NodeList (DOM-коллекции)
javascript
const divs = document.querySelectorAll('div');
for (const div of divs) {
console.log(div.textContent);
}
// Превращение в массив
const divsArray = [...divs];
Часть 6. Создание своих перебираемых объектов
6.1 Диапазон чисел
javascript
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const numbers = new Range(1, 10, 2);
console.log([...numbers]); // [1, 3, 5, 7, 9]
6.2 Бесконечная последовательность (Фибоначчи)
javascript
const fibonacci = {
[Symbol.iterator]() {
let prev = 0;
let curr = 1;
return {
next() {
const value = curr;
[prev, curr] = [curr, prev + curr];
return { value, done: false }; // никогда не закончится!
}
};
}
};
// Берём первые 10 чисел
const first10 = [];
let i = 0;
for (const num of fibonacci) {
if (i++ >= 10) break;
first10.push(num);
}
console.log(first10); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
6.3 Перебор объекта (по значениям)
javascript
const obj = { a: 1, b: 2, c: 3 };
// Обычный объект не является перебираемым
// for (const value of obj) {} // TypeError!
// Сделаем его перебираемым
Object.prototype[Symbol.iterator] = function() {
const values = Object.values(this);
let index = 0;
return {
next: () => {
if (index < values.length) {
return { value: values[index++], done: false };
}
return { value: undefined, done: true };
}
};
};
// Теперь работает (но лучше так не делать — загрязнение прототипа!)
for (const value of obj) {
console.log(value); // 1, 2, 3
}
// Лучше: отдельная функция
function objectIterator(obj) {
const values = Object.values(obj);
let index = 0;
return {
[Symbol.iterator]() { return this; },
next() {
if (index < values.length) {
return { value: values[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
for (const value of objectIterator(obj)) {
console.log(value); // 1, 2, 3
}
Часть 7. Итераторы вручную
7.1 Создание итератора без объекта
javascript
function makeIterator(array) {
let index = 0;
return {
next() {
if (index < array.length) {
return { value: array[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
const iter = makeIterator([1, 2, 3]);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }
7.2 Итератор, который можно использовать в for...of
Чтобы итератор работал с for...of, он должен сам быть перебираемым (иметь [Symbol.iterator], возвращающий себя).
javascript
function createIterable(array) {
let index = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < array.length) {
return { value: array[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
const iterable = createIterable([1, 2, 3]);
for (const value of iterable) {
console.log(value); // 1, 2, 3
}
Часть 8. Ленивые вычисления через итераторы
Главная суперсила итераторов - ленивость. Значения вычисляются только тогда, когда они нужны.
javascript
// Бесконечный итератор случайных чисел
const randomNumbers = {
[Symbol.iterator]() {
return {
next() {
return { value: Math.random(), done: false };
}
};
}
};
// Берём только 5 чисел
const fiveRandoms = [];
for (const num of randomNumbers) {
if (fiveRandoms.length >= 5) break;
fiveRandoms.push(num);
}
console.log(fiveRandoms); // 5 случайных чисел
// А если бы мы создали массив из 1000000 случайных чисел - память бы переполнилась
// С итератором мы берём столько, сколько нужно
Часть 9. Spread и деструктуризация с итераторами
... (spread) и деструктуризация массива работают с любыми перебираемыми объектами.
javascript
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
// Spread
const arr = [...range];
console.log(arr); // [1, 2, 3, 4, 5]
// Деструктуризация
const [first, second, ...rest] = range;
console.log(first, second, rest); // 1, 2, [3, 4, 5]
// Array.from
const arr2 = Array.from(range);
console.log(arr2); // [1, 2, 3, 4, 5]
Часть 10. Генераторы - фабрики итераторов
Генераторы — это удобный способ создания итераторов. Мы подробно разберём их в отдельной статье, но базовое понимание важно.
javascript
// Генератор - функция, которая возвращает итератор
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
const numbers = range(1, 5);
console.log([...numbers]); // [1, 2, 3, 4, 5]
// Генератор для бесконечной последовательности
function* fibonacci() {
let prev = 0;
let curr = 1;
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
Часть 11. Проверка на перебираемость
javascript
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable({})); // false
console.log(isIterable(null)); // false
console.log(isIterable(42)); // false
Часть 12. Реальные кейсы
12.1 Пагинация через итератор
javascript
async function* paginatedFetch(url, pageSize = 10) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?_page=${page}&_limit=${pageSize}`);
const data = await response.json();
if (data.length === 0) {
hasMore = false;
} else {
yield data;
page++;
}
}
}
// Использование
for await (const page of paginatedFetch('/api/users')) {
console.log(`Получена страница с ${page.length} пользователями`);
// Обработка страницы
}
12.2 Обработка больших файлов
javascript
function* chunkReader(string, chunkSize = 100) {
for (let i = 0; i < string.length; i += chunkSize) {
yield string.slice(i, i + chunkSize);
}
}
const hugeString = "x".repeat(1000000);
let processed = 0;
for (const chunk of chunkReader(hugeString, 1000)) {
processChunk(chunk);
processed += chunk.length;
console.log(`Обработано: ${processed} символов`);
}
12.3 Древовидный обход (DFS)
javascript
function* traverseTree(node) {
yield node.value;
if (node.children) {
for (const child of node.children) {
yield* traverseTree(child);
}
}
}
const tree = {
value: 1,
children: [
{ value: 2, children: [{ value: 4 }, { value: 5 }] },
{ value: 3, children: [{ value: 6 }, { value: 7 }] }
]
};
for (const value of traverseTree(tree)) {
console.log(value); // 1, 2, 4, 5, 3, 6, 7
}
Часть 13. Подводные камни
Камень #1: Объекты не перебираемы по умолчанию
javascript
const obj = { a: 1, b: 2 };
for (const value of obj) {} // TypeError!
// Решение: использовать Object.values
for (const value of Object.values(obj)) {}
Камень #2: Нельзя прервать forEach, но можно прервать for...of
javascript
const arr = [1, 2, 3, 4, 5];
// forEach нельзя прервать
arr.forEach(n => {
if (n === 3) break; // SyntaxError!
});
// for...of можно
for (const n of arr) {
if (n === 3) break; // работает
}
Камень #3: Итераторы одноразовые
javascript
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
console.log([...iter]); // [1, 2, 3]
console.log([...iter]); // [] (пусто! итератор исчерпан)
Итог: Манифест итераторов
- Перебираемый объект имеет метод [Symbol.iterator](), возвращающий итератор.
- Итератор имеет метод next(), возвращающий { value, done }.
- for...of - универсальный способ перебора перебираемых объектов.
- Массивы, строки, Map, Set, arguments, NodeList - встроенные перебираемые объекты.
- Spread (...) и деструктуризация работают с любыми перебираемыми объектами.
- Генераторы (function*) - удобный способ создания итераторов.
- Ленивые вычисления - главная суперсила итераторов (бесконечные последовательности).
- Итераторы одноразовые - после прохода их нельзя использовать снова.
Финальный тест (что выведет?):
javascript
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
console.log([...range]);
console.log([...range]);
for (const num of range) {
console.log(num);
}
const [first, second] = range;
console.log(first, second);
Ответ: [1, 2, 3], [1, 2, 3] (каждый раз новый итератор), 1 2 3, 1 2.
Итераторы и перебираемые объекты - это мост между данными и их обработкой. Они позволяют абстрагироваться от структуры данных и работать с ними единообразно. А возможность создавать свои итераторы открывает дверь в мир ленивых вычислений и эффективной обработки больших данных. Освойте этот механизм, и ваш код станет гибче и выразительнее.