Добавить в корзинуПозвонить
Найти в Дзене
ovnoCod

Язык JavaScript - Типы данных

Вы когда-нибудь замечали странность? 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 - механизм, который заставляет примитивы вести себя как объекты. Это не магия, это тщательно продуманная система объектов-обёрток. Начнём с основ. В 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 ра
Оглавление
картинка взята с сайта ya.ru
картинка взята с сайта ya.ru

Иллюзия величия: Почему примитивы умеют вызывать методы (и как это работает)

Вы когда-нибудь замечали странность?

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. Итоговая таблица

-2

Итог: Манифест методов примитивов

  1. Примитивы не имеют свойств - это иллюзия, создаваемая временными объектами-обёртками.
  2. Три основные обёртки: String, Number, Boolean.
  3. Не создавайте обёртки явно - используйте примитивы.
  4. Временный объект живёт одну операцию - свойства, добавленные к нему, теряются.
  5. null и undefined не имеют обёрток - вызов метода приведёт к ошибке.
  6. Для чисел с точкой - используйте две точки или скобки.
  7. Булева обёртка всегда true - даже если внутри false.
  8. Примитивы быстрее и безопаснее - всегда используйте их.

Финальный тест (что выведет?):

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

Итог: Манифест чисел

  1. Все числа - 64-битные float - нет отдельного типа для целых.
  2. 0.1 + 0.2 !== 0.3 - из-за двоичного представления. Используйте погрешность.
  3. NaN не равен сам себе - проверяйте через Number.isNaN().
  4. Infinity и -Infinity - результат деления на ноль.
  5. BigInt - для целых чисел больше 2^53 - 1.
  6. toFixed возвращает строку - не забывайте преобразовывать обратно.
  7. Для финансов - работайте в копейках/центах (целых числах) или используйте 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 '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
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);
// "Пользователь ввёл: &lt;script&gt;alert('xss')&lt;/script&gt;"

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

Итог: Манифест строк

  1. Строки неизменяемы - любая операция создаёт новую строку.
  2. Три типа кавычек - одинарные, двойные, обратные (шаблонные).
  3. Шаблонные строки - интерполяция, многострочность, тегирование.
  4. Методы не меняют оригинал - всегда возвращают новую строку.
  5. length - количество символов (осторожно с эмодзи).
  6. slice - самый гибкий способ извлечения (поддерживает отрицательные индексы).
  7. trim - всегда чистите пользовательский ввод.
  8. includes / startsWith / endsWith - современные методы поиска.
  9. Регулярные выражения - мощь для сложного поиска и замены.

Финальный тест (проверьте себя):

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

Итог: Манифест массивов

  1. Массивы - это объекты с числовыми ключами и свойством length.
  2. length можно изменять - обрезать и расширять массив.
  3. Методы делятся на мутирующие и немутирующие:
    Мутируют: push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin
    Не мутируют: slice, concat, map, filter, reduce, forEach, find, includes
  4. sort() без функции сравнения - сортирует как строки (коварно!).
  5. Array.isArray() - единственный надёжный способ проверки.
  6. Деструктуризация - удобно для обмена переменных и извлечения элементов.
  7. 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;
}

Итог: Шпаргалка методов

-3

Финальный тест (что выведет?):

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. Протокол итерации

Протокол итерации состоит из двух частей:

  1. Iterable - объект, у которого есть метод [Symbol.iterator](), возвращающий итератор.
  2. 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]);
// [] (пусто! итератор исчерпан)

Итог: Манифест итераторов

  1. Перебираемый объект имеет метод [Symbol.iterator](), возвращающий итератор.
  2. Итератор имеет метод next(), возвращающий { value, done }.
  3. for...of - универсальный способ перебора перебираемых объектов.
  4. Массивы, строки, Map, Set, arguments, NodeList - встроенные перебираемые объекты.
  5. Spread (...) и деструктуризация работают с любыми перебираемыми объектами.
  6. Генераторы (function*) - удобный способ создания итераторов.
  7. Ленивые вычисления - главная суперсила итераторов (бесконечные последовательности).
  8. Итераторы одноразовые - после прохода их нельзя использовать снова.

Финальный тест (что выведет?):

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.

Итераторы и перебираемые объекты - это мост между данными и их обработкой. Они позволяют абстрагироваться от структуры данных и работать с ними единообразно. А возможность создавать свои итераторы открывает дверь в мир ленивых вычислений и эффективной обработки больших данных. Освойте этот механизм, и ваш код станет гибче и выразительнее.