Массивы в JavaScript: добавление, удаление и поиск элементов

JavaScript Автор: Среда и версия: Node.js 20.20.2, Linux; примеры и проверка с node:assert выполнены локально
содержание

Массив в JavaScript хранит последовательность элементов, доступных по индексу: orders[0] — первый заказ, orders[1] — второй. Для добавления в конец подходит push, для удаления по индексу — splice, для отбора по условию — filter, для поиска одного элемента — find.

Перед выбором метода реши, нужен ли новый массив. push и splice меняют исходный; filter создаёт другой массив. Это различие особенно заметно, когда один список используют несколько частей программы.

Создание массива, индексы и length

Возьмём три заказа. Каждый элемент — объект с идентификатором, суммой и статусом:

const orders = [
  { id: 101, total: 1200, status: 'paid' },
  { id: 102, total: 800, status: 'new' },
  { id: 103, total: 500, status: 'paid' },
];

console.log(orders.length);      // 3
console.log(orders[0].id);       // 101
console.log(orders.at(-1).id);   // 103
console.log(orders[3]);          // undefined
console.log(Array.isArray(orders)); // true

У этого массива индексы идут от 0 до 2. id: 101 — поле заказа, оно не связано с позицией элемента. Выражение orders[101] не ищет заказ с таким идентификатором.

Пустой массив записывается как []. new Array(3) создаёт массив длиной три с пустыми позициями, а не массив [3]. В обычном списке без пропусков length совпадает с числом элементов. После записи по далёкому индексу или удаления через delete это уже не так. Различие между длиной и заполненными позициями описано в справке MDN об Array.

Если данные пришли извне, сначала проверь ожидаемую форму. Array.isArray(value) проверяет сам массив, но не поля его элементов: для ответа API с заказами отдельно нужны проверки id, total и status.

Как добавить элемент в массив

push дописывает элемент в конец и возвращает новую длину:

const orders = [{ id: 101 }, { id: 102 }];
const length = orders.push({ id: 103 });

console.log(length);                  // 3
console.log(orders.map(o => o.id));    // [101, 102, 103]

Ошибка orders = orders.push(newOrder) имеет две причины: переменной const нельзя присвоить другое значение, а push возвращает число. Даже с let такая запись заменит массив его длиной.

Если исходный список нужен дальше без изменений, собери новый:

const orders = [{ id: 101 }, { id: 102 }];
const extended = [...orders, { id: 103 }];

console.log(orders.length);   // 2
console.log(extended.length); // 3

Для добавления в начало есть unshift. Принцип тот же: orders.unshift(newOrder) меняет исходный массив, [newOrder, ...orders] создаёт новый. const запрещает менять привязку переменной, но разрешает изменять содержимое массива.

Удаление по индексу: splice и ловушка delete

Чтобы удалить второй заказ и сдвинуть следующие элементы, передай в splice начальный индекс и число удаляемых элементов:

const orders = [{ id: 101 }, { id: 102 }, { id: 103 }];
const removed = orders.splice(1, 1);

console.log(removed.map(o => o.id)); // [102]
console.log(orders.map(o => o.id));  // [101, 103]
console.log(orders.length);         // 2

splice(1, 1) удаляет один элемент начиная с индекса 1. Возвращаемое значение — массив удалённых элементов. Если передать только splice(1), удалятся все элементы от второго до конца. Отрицательный индекс отсчитывается с конца, поэтому splice(-1, 1) удаляет последний элемент. Параметры splice в MDN.

JavaScript / 01

splice удаляет позицию и сдвигает остаток

Три заказа превращаются в два после splice(1, 1)До операции индексы 0, 1 и 2 содержат заказы 101, 102 и 103. Метод splice удаляет заказ 102, а заказ 103 перемещается с индекса 2 на индекс 1. Длина становится равна двум.01 / до: length = 302 / после: length = 2101102103индекс 0индекс 1индекс 2101103splice(1, 1)удалён заказ 102заказ 103 теперь под индексом 1индекс 0индекс 1
После splice(1, 1) длина уменьшается до двух. Идентификатор заказа 103 сохраняется, его индекс меняется.

delete orders[1] ведёт себя иначе:

const ids = [101, 102, 103];
delete ids[1];

console.log(ids.length); // 3
console.log(ids[1]);     // undefined
console.log(1 in ids);   // false
console.log(ids[2]);     // 103

Позиция 1 стала пустой, а заказ 103 остался на позиции 2. Для обычного списка заказов такая дыра не нужна. delete удаляет свойство массива и не меняет length; это отдельно отмечено в документации оператора.

Новый массив после удаления по индексу можно получить через orders.toSpliced(1, 1). Он доступен в использованном здесь Node.js 20.20.2; совместимость с целевыми браузерами проверь по таблице MDN. Для более старой среды можно сначала скопировать массив: const copy = [...orders], затем вызвать copy.splice(1, 1).

Удаление по условию через filter

Индекс может измениться после сортировки или предыдущего удаления. Если известен id, отбирай заказы по нему:

const orders = [
  { id: 101, status: 'paid' },
  { id: 102, status: 'new' },
  { id: 103, status: 'paid' },
];
const remaining = orders.filter(order => order.id !== 102);
const paid = orders.filter(order => order.status === 'paid');

console.log(remaining.map(o => o.id)); // [101, 103]
console.log(paid.map(o => o.id));      // [101, 103]
console.log(orders.length);           // 3

Условие в filter описывает, кого оставить. Для удаления заказа 102 используем !== 102. Метод проверяет элементы и возвращает новый массив подходящих; исходный список сохраняется, если сама функция условия его не меняет. Описание filter в MDN.

Если идентификаторы повторяются, этот пример уберёт все заказы с id: 102. Для удаления только первого совпадения найди индекс и обязательно проверь его:

const orders = [{ id: 101 }, { id: 102 }, { id: 103 }];
const index = orders.findIndex(order => order.id === 999);

if (index !== -1) {
  orders.splice(index, 1);
}

console.log(orders.map(o => o.id)); // [101, 102, 103]

findIndex вернул -1, потому что заказа 999 нет. Без проверки splice(-1, 1) удалил бы последний заказ. Для индекса проверка if (index) тоже неверна: индекс 0 считается ложным, а -1 — истинным.

Поиск: find, includes или filter

find возвращает первый подходящий элемент, а если совпадений нет — undefined:

const orders = [{ id: 101, total: 1200 }, { id: 102, total: 800 }];
const order = orders.find(item => item.id === 102);

if (order === undefined) {
  console.log('Заказ не найден');
} else {
  console.log(order.total); // 800
}

Обращение orders.find(item => item.id === 999).total упадёт: читать total не у чего. Разбор этой ситуации есть в статье Cannot read properties of undefined. Если заказ обязателен, сообщи об ошибке; если нет, обработай отсутствие результата.

Для массива самих идентификаторов удобно ids.includes(102). Для объектов orders.includes({ id: 102 }) не проверяет поля: отдельно созданный объект имеет другую ссылку. Поиск по полю выполняй условием.

Что нужно получитьМетодЕсли ничего нет
Первый заказ по условиюfindundefined
Индекс первого заказаfindIndex-1
Есть ли подходящий заказsomefalse
Все подходящие заказыfilter[]
Есть ли конкретное значениеincludesfalse

В массиве, где допустим сам undefined, одного результата find недостаточно, чтобы отличить найденный undefined от отсутствия совпадения. Для проверки наличия используй some, для позиции — findIndex. Ещё одна особенность: find посещает пустые позиции как undefined. Если список получился разреженным, обращение к полю внутри условия может упасть. Возвращаемое значение и пустые позиции в MDN.

Новый массив ещё не означает новые объекты

Копирование через ... создаёт новый внешний массив. Объекты заказов остаются общими:

const orders = [{ id: 101, status: 'new' }];
const copy = [...orders];
copy[0].status = 'paid';

console.log(copy === orders);       // false
console.log(copy[0] === orders[0]);  // true
console.log(orders[0].status);       // paid

Такая же поверхностная копия получается у filter: он не клонирует найденные объекты. Если нужно изменить статус одного заказа без изменения исходных объектов, создай новый объект именно для него:

const orders = [{ id: 101, status: 'new' }, { id: 102, status: 'new' }];
const updated = orders.map(order =>
  order.id === 101 ? { ...order, status: 'paid' } : order
);

console.log(orders[0].status);       // new
console.log(updated[0].status);      // paid
console.log(updated[1] === orders[1]); // true

Этот пример копирует один уровень объекта. Если внутри заказа появится объект address, для изменения address.city понадобится отдельно скопировать address. Здесь вложенных данных нет, поэтому глубокое копирование не требуется.

Проверка на Node.js

Сохрани код в arrays-check.mjs и запусти node arrays-check.mjs. Проверка охватывает удаление, ненайденный индекс и общие ссылки на объекты:

import assert from 'node:assert/strict';

const orders = [{ id: 101 }, { id: 102 }, { id: 103 }];
assert.deepEqual(orders.filter(o => o.id !== 102).map(o => o.id), [101, 103]);
assert.equal(orders.length, 3);
assert.equal(orders.find(o => o.id === 999), undefined);
assert.equal(orders.findIndex(o => o.id === 101), 0);

const missing = orders.findIndex(o => o.id === 999);
if (missing !== -1) orders.splice(missing, 1);
assert.deepEqual(orders.map(o => o.id), [101, 102, 103]);

const copy = [...orders];
assert.deepEqual(copy.splice(1, 1), [{ id: 102 }]);
assert.deepEqual(copy.map(o => o.id), [101, 103]);
copy[0].id = 500;
assert.equal(orders[0].id, 500);

const ids = [101, 102, 103];
delete ids[1];
assert.equal(ids.length, 3);
assert.equal(1 in ids, false);
assert.deepEqual([101, 102, 103].toSpliced(1, 1), [101, 103]);
console.log('Проверки массивов пройдены');

Вернись к примеру удаления через findIndex, замени искомый id на 101 и проверь результат: должны остаться заказы 102 и 103.

Источники