Все методы сведены к одному

Массивы предоставляют множество методов. Чтобы упростить задачу, в этой главе они разделены на группы.

Добавить/удалить элементы

Мы уже знаем методы, которые добавляют и удаляют элементы с начала или с конца:

  • arr.push(...items) — добавляет элементы в конец,
  • arr.pop() — извлекает элемент с конца,
  • arr.shift() — извлекает элемент с самого начала,
  • arr.unshift(...items) — добавляет элементы в начало.

Вот несколько других.

сращивание

Как удалить элемент из массива?

Массивы являются объектами, поэтому мы можем попробовать использовать delete:

let arr = ["I", "go", "home"];
delete arr[1]; // remove "go"
alert( arr[1] ); // undefined
// now arr = ["I",  , "home"];
alert( arr.length ); // 3

Элемент был удален, но в массиве осталось 3 элемента, мы видим, что arr.length == 3.

Это естественно, потому что delete obj.key удаляет значение по key. Это все, что он делает. Хорошо для объектов. Но для массивов мы обычно хотим, чтобы остальные элементы сместились и заняли освободившееся место. Мы ожидаем, что теперь у нас будет более короткий массив.

Поэтому следует использовать специальные методы.

Метод arr.splice — это швейцарский армейский нож для массивов. Он умеет все: вставлять, удалять и заменять элементы.

Синтаксис:

arr.splice(start[, deleteCount, elem1, ..., elemN])

Он изменяет arr, начиная с индекса start: удаляет deleteCount элементов, а затем вставляет elem1, ..., elemN на их место. Возвращает массив удаленных элементов.

Этот метод легко понять на примерах.

Начнем с удаления:

let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // from index 1 remove 1 element
alert( arr ); // ["I", "JavaScript"]

Легко, верно? Начиная с индекса 1 удаляется 1 элемент.

В следующем примере мы удаляем 3 элемента и заменяем их двумя другими:

let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 3 first elements and replace them with another
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // now ["Let's", "dance", "right", "now"]

Здесь мы видим, что splice возвращает массив удаленных элементов:

let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 2 first elements
let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- array of removed elements

Метод splice также может вставлять элементы без удаления. Для этого нам нужно установить deleteCount в 0:

let arr = ["I", "study", "JavaScript"];
// from index 2
// delete 0
// then insert "complex" and "language"
arr.splice(2, 0, "complex", "language");
alert( arr ); // "I", "study", "complex", "language", "JavaScript"

Разрешены отрицательные индексы

Здесь и в других методах массива допускаются отрицательные индексы. Они указывают позицию с конца массива, как здесь:

let arr = [1, 2, 5];
// from index -1 (one step from the end)
// delete 0 elements,
// then insert 3 and 4
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5

"кусочек"

Метод arr.slice намного проще похожего на него arr.splice.

Синтаксис:

arr.slice([start], [end])

Он возвращает новый массив, копируя в него все элементы с индекса start по end (не включая end). И start, и end могут быть отрицательными, в этом случае предполагается позиция от конца массива.

Он похож на строковый метод str.slice, но вместо подстрок создает подмассивы.

Например:

let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s (copy from 1 to 3)
alert( arr.slice(-2) ); // s,t (copy from -2 till the end)

Мы также можем вызвать его без аргументов: arr.slice() создает копию arr. Это часто используется для получения копии для дальнейших преобразований, которые не должны влиять на исходный массив.

конкат

Метод arr.concat создает новый массив, включающий значения из других массивов и дополнительные элементы.

Синтаксис:

arr.concat(arg1, arg2...)

Он принимает любое количество аргументов — либо массивы, либо значения.

Результатом является новый массив, содержащий элементы из arr, затем arg1, arg2 и т. д.

Если аргумент argN является массивом, то копируются все его элементы. В противном случае копируется сам аргумент.

Например:

let arr = [1, 2];
// create an array from: arr and [3,4]
alert( arr.concat([3, 4]) ); // 1,2,3,4
// create an array from: arr and [3,4] and [5,6]
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// create an array from: arr and [3,4], then add values 5 and 6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

Обычно он копирует только элементы из массивов. Другие объекты, даже если они выглядят как массивы, добавляются целиком:

let arr = [1, 2];
let arrayLike = {
  0: "something",
  length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]

…Но если массивоподобный объект имеет специальное свойство Symbol.isConcatSpreadable, то concat обрабатывает его как массив: вместо него добавляются его элементы:

let arr = [1, 2];
let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else

Итерировать: для каждого

Метод arr.forEach позволяет запускать функцию для каждого элемента массива.

Синтаксис:

arr.forEach(function(item, index, array) {
  // ... do something with item
});

Например, это показывает каждый элемент массива:

// for each element call alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

И этот код более подробно описывает их позиции в целевом массиве:

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

Результат функции (если она его возвращает) отбрасывается и игнорируется.

Поиск в массиве

Теперь давайте рассмотрим методы, выполняющие поиск в массиве.

indexOf/lastIndexOf и включает

Методы arr.indexOf и arr.includes имеют аналогичный синтаксис и делают то же самое, что и их строковые аналоги, но работают с элементами, а не с символами:

  • arr.indexOf(item, from) — ищет item, начиная с индекса from, и возвращает индекс, в котором он был найден, иначе -1.
  • arr.includes(item, from) — ищет item, начиная с индекса from, возвращает true, если находит.

Обычно эти методы используются только с одним аргументом: item для поиска. По умолчанию поиск идет с начала.

Например:

let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true

Обратите внимание, что indexOf использует для сравнения строгое равенство ===. Итак, если мы ищем false, он находит именно false, а не ноль.

Если мы хотим проверить, существует ли item в массиве, и нам не нужен индекс, то предпочтительнее arr.includes.

Метод arr.lastIndexOf аналогичен indexOf, но ищет справа налево.

let fruits = ['Apple', 'Orange', 'Apple']
alert( fruits.indexOf('Apple') ); // 0 (first Apple)
alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple)

Метод includes правильно обрабатывает NaN

Незначительная, но заслуживающая внимания особенность includes заключается в том, что он правильно обрабатывает NaN, в отличие от indexOf:

const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0)
alert( arr.includes(NaN) );// true (correct)

Это связано с тем, что includes был добавлен в JavaScript намного позже и внутри использует более современный алгоритм сравнения.

найти и найтиИндекс/найтиПоследнийИндекс

Представьте, что у нас есть массив объектов. Как найти объект с определенным условием?

Здесь на помощь приходит метод arr.find(fn).

Синтаксис:

let result = arr.find(function(item, index, array) {
  // if true is returned, item is returned and iteration is stopped
  // for falsy scenario returns undefined
});

Функция вызывается для элементов массива один за другим:

  • item — это элемент.
  • index — его индекс.
  • array — это сам массив.

Если он возвращает true, поиск останавливается, возвращается item. Если ничего не найдено, возвращается undefined.

Например, у нас есть массив пользователей, у каждого из которых есть поля id и name. Найдем тот, у которого id == 1:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];
let user = users.find(item => item.id == 1);
alert(user.name); // John

В реальной жизни массивы объектов — обычное дело, поэтому метод find очень полезен.

Обратите внимание, что в примере мы предоставляем find функцию item => item.id == 1 с одним аргументом. Это типично, другие аргументы этой функции редко используются.

Метод arr.findIndex имеет тот же синтаксис, но вместо самого элемента возвращает индекс, в котором был найден элемент. Значение -1 возвращается, если ничего не найдено.

Метод arr.findLastIndex аналогичен findIndex, но ищет справа налево, аналогично lastIndexOf.

Вот пример:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"},
  {id: 4, name: "John"}
];
// Find the index of the first John
alert(users.findIndex(user => user.name == 'John')); // 0
// Find the index of the last John
alert(users.findLastIndex(user => user.name == 'John')); // 3

"фильтр"

Метод find ищет единственный (первый) элемент, который возвращает функцию true.

Если их может быть много, мы можем использовать arr.filter(fn).

Синтаксис подобен find, но filter возвращает массив всех совпадающих элементов:

let results = arr.filter(function(item, index, array) {
  // if true item is pushed to results and the iteration continues
  // returns empty array if nothing found
});

Например:

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];
// returns array of the first two users
let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2

Преобразовать массив

Давайте перейдем к методам, которые преобразуют и переупорядочивают массив.

"карта"

Метод arr.map — один из самых полезных и часто используемых.

Он вызывает функцию для каждого элемента массива и возвращает массив результатов.

Синтаксис:

let result = arr.map(function(item, index, array) {
  // returns the new value instead of item
});

Например, здесь мы преобразуем каждый элемент в его длину:

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6

сортировать(фн)

Вызов arr.sort() сортирует массив по месту, изменяя порядок его элементов.

Он также возвращает отсортированный массив, но возвращаемое значение обычно игнорируется, так как сам arr модифицируется.

Например:

let arr = [ 1, 2, 15 ];
// the method reorders the content of arr
arr.sort();
alert( arr );  // 1, 15, 2

Вы не заметили ничего странного в результате?

Заказ стал 1, 15, 2. Неправильно. Но почему?

По умолчанию элементы сортируются как строки.

Буквально все элементы преобразуются в строки для сравнения. Для строк применяется лексикографический порядок и действительно "2" > "15".

Чтобы использовать наш собственный порядок сортировки, нам нужно предоставить функцию в качестве аргумента arr.sort().

Функция должна сравнить два произвольных значения и вернуть:

function compare(a, b) {
  if (a > b) return 1; // if the first value is greater than the second
  if (a == b) return 0; // if values are equal
  if (a < b) return -1; // if the first value is less than the second
}

Например, для сортировки по числам:

function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr);  // 1, 2, 15

Теперь он работает как задумано.

Давайте отойдем в сторону и подумаем, что происходит. arr может быть массивом чего угодно, верно? Он может содержать числа, строки, объекты или что-то еще. У нас есть набор некоторых элементов. Чтобы его отсортировать, нам нужна функция упорядочивания, которая знает, как сравнивать его элементы. По умолчанию используется строковый порядок.

Метод arr.sort(fn) реализует общий алгоритм сортировки. Нам не нужно заботиться о том, как это работает внутри (в большинстве случаев это оптимизированная быстрая сортировка или Timsort). Он будет проходить по массиву, сравнивать его элементы с помощью предоставленной функции и переупорядочивать их, все, что нам нужно, это предоставить fn, который выполняет сравнение.

Кстати, если мы когда-нибудь захотим узнать, какие элементы сравниваются — ничто не мешает их предупредить:

[1, -2, 15, 2, 0, 8].sort(function(a, b) {
  alert( a + " <> " + b );
  return a - b;
});

Алгоритм может сравнивать элемент с несколькими другими в процессе, но он пытается сделать как можно меньше сравнений.

Функция сравнения может возвращать любое число

На самом деле функция сравнения требуется только для того, чтобы возвращать положительное число, чтобы сказать «больше», и отрицательное число, чтобы сказать «меньше».

Это позволяет писать более короткие функции:

let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
alert(arr);  // 1, 2, 15

Стрелочные функции для лучших

Помните стрелочные функции? Мы можем использовать их здесь для более аккуратной сортировки:

arr.sort( (a, b) => a - b );

Это работает точно так же, как и более длинная версия выше.

Используйте localeCompare для строк

Помните алгоритм сравнения строк? По умолчанию он сравнивает буквы по их кодам.

Для многих алфавитов лучше использовать метод str.localeCompare для правильной сортировки букв, например Ö.

Например, давайте отсортируем несколько стран на немецком языке:

let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong)
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!)

"обеспечить регресс"

Метод arr.reverse меняет порядок элементов в arr на обратный.

Например:

let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1

Он также возвращает массив arr после разворота.

разделиться и присоединиться

Вот ситуация из реальной жизни. Мы пишем приложение для обмена сообщениями, и человек вводит список получателей через запятую: John, Pete, Mary. Но для нас массив имен был бы гораздо удобнее, чем одна строка. Как его получить?

Именно это и делает метод str.split(delim). Он разбивает строку на массив по заданному разделителю delim.

В приведенном ниже примере мы разделяем запятую, за которой следует пробел:

let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo  (and other names)
}

Метод split имеет необязательный второй числовой аргумент — ограничение на длину массива. Если он указан, то лишние элементы игнорируются. Однако на практике он используется редко:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf

Разбить на буквы

Вызов split(s) с пустым s разбил бы строку на массив букв:

let str = "test";
alert( str.split('') ); // t,e,s,t

Вызов arr.join(клей) делает обратное split. Он создает строку из arr элементов, соединенных glue между ними.

Например:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // glue the array into a string using ;
alert( str ); // Bilbo;Gandalf;Nazgul

уменьшить/свернуть вправо

Когда нам нужно перебрать массив — мы можем использовать forEach, for или for..of.

Когда нам нужно выполнить итерацию и вернуть данные для каждого элемента — мы можем использовать map.

Методы arr.reduce и arr.reduceRight тоже относятся к этой породе, но немного сложнее. Они используются для вычисления одного значения на основе массива.

Синтаксис:

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

Функция последовательно применяется ко всем элементам массива и «переносит» свой результат на следующий вызов.

Аргументы:

  • accumulator – результат предыдущего вызова функции, равен initial в первый раз (если указано initial).
  • item — текущий элемент массива.
  • index – его позиция.
  • array — это массив.

При применении функции результат предыдущего вызова функции передается следующей в качестве первого аргумента.

Таким образом, первый аргумент — это, по сути, аккумулятор, в котором хранится объединенный результат всех предыдущих выполнений. И в конце становится результатом reduce.

Звучит сложно?

Легче всего понять это на примере.

Здесь мы получаем сумму массива в одну строку:

let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15

Функция, переданная reduce, использует только 2 аргумента, обычно этого достаточно.

Давайте посмотрим подробности того, что происходит.

  1. При первом запуске sum — это значение initial (последний аргумент reduce), равное 0, а current — первый элемент массива, равный 1. Таким образом, результатом функции является 1.
  2. При втором прогоне, sum = 1, мы добавляем к нему второй элемент массива (2) и возвращаемся.
  3. На третьем прогоне sum = 3 и мы добавляем к нему еще один элемент, и так далее...

Поток расчета:

Или в виде таблицы, где каждая строка представляет собой вызов функции на следующем элементе массива:

sumcurrentрезультатпервого звонка011второго звонка123третьего звонка336четвертого звонка6410пятого звонка10515

Здесь хорошо видно, как результат предыдущего вызова становится первым аргументом следующего.

Мы также можем опустить начальное значение:

let arr = [1, 2, 3, 4, 5];
// removed initial value from reduce (no 0)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15

Результат тот же. Это потому, что если нет начального, то reduce берет первый элемент массива в качестве начального значения и начинает итерацию со 2-го элемента.

Таблица расчета такая же, как и выше, за вычетом первой строки.

Но такое использование требует крайней осторожности. Если массив пуст, то вызов reduce без начального значения выдает ошибку.

Вот пример:

let arr = [];
// Error: Reduce of empty array with no initial value
// if the initial value existed, reduce would return it for the empty arr.
arr.reduce((sum, current) => sum + current);

Поэтому рекомендуется всегда указывать начальное значение.

Метод arr.reduceRight делает то же самое, но идет справа налево.

Массив.естьМассив

Массивы не образуют отдельный тип языка. Они основаны на объектах.

Таким образом, typeof не помогает отличить простой объект от массива:

alert(typeof {}); // object
alert(typeof []); // object (same)

…Но массивы используются так часто, что для этого есть специальный метод: Array.isArray(value). Он возвращает true, если value является массивом, и false в противном случае.

alert(Array.isArray({})); // false
alert(Array.isArray([])); // true

Большинство методов поддерживают «thisArg»

Почти все методы массива, вызывающие функции, такие как find, filter, map, за заметным исключением sort, принимают необязательный дополнительный параметр thisArg.

Этот параметр не объясняется в разделах выше, потому что он редко используется. Но для полноты мы должны покрыть это.

Вот полный синтаксис этих методов:

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg is the optional last argument

Значение параметра thisArg становится this для func.

Например, здесь мы используем метод объекта army в качестве фильтра, а thisArg передает контекст:

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};
let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];
// find users, for who army.canJoin returns true
let soldiers = users.filter(army.canJoin, army);
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

Если бы в приведенном выше примере мы использовали users.filter(army.canJoin), то army.canJoin вызывалась бы как отдельная функция с this=undefined, что приводило бы к мгновенной ошибке.

Вызов users.filter(army.canJoin, army) можно заменить на users.filter(user => army.canJoin(user)), который делает то же самое. Последний используется чаще, так как его немного легче понять большинству людей.

"Краткое содержание"

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

Чтобы добавить/удалить элементы:

  • push(...items) — добавляет элементы в конец,
  • pop() — извлекает элемент с конца,
  • shift() — извлекает элемент с самого начала,
  • unshift(...items) — добавляет элементы в начало.
  • splice(pos, deleteCount, ...items) — по индексу pos удаляет deleteCount элементов и вставляет items.
  • slice(start, end) — создает новый массив, копирует в него элементы с индекса start по end (не включительно).
  • concat(...items) — возвращает новый массив: копирует все элементы текущего и добавляет к нему items. Если какой-либо из items является массивом, то берутся его элементы.

Чтобы выполнить поиск среди элементов:

  • indexOf/lastIndexOf(item, pos) — искать item начиная с позиции pos, возвращать индекс или -1, если не найдено.
  • includes(value) — возвращает true, если в массиве есть value, иначе false.
  • find/filter(func) — фильтровать элементы через функцию, возвращать первое/все значения, которые заставляют возвращать true.
  • findIndex похож на find, но вместо значения возвращает индекс.

Чтобы перебирать элементы:

  • forEach(func) — вызывает func для каждого элемента, ничего не возвращает.
  • Чтобы преобразовать массив:
  • map(func) — создает новый массив из результатов вызова func для каждого элемента.
  • sort(func) — сортирует массив на месте, а затем возвращает его.
  • reverse() — переворачивает массив на месте, затем возвращает его.
  • split/join — преобразовать строку в массив и обратно.
  • reduce/reduceRight(func, initial) — вычислить одно значение по массиву, вызывая func для каждого элемента и передавая промежуточный результат между вызовами.