Методы массивов

На этом занятии рассмотрим основные методы массивов. Некоторые из них мы уже рассмотрели на предыдущем занятии – это push/pop и shift/unshift. Здесь мы продолжим эту тему и чтобы было проще воспринимать материал, все методы разбиты на группы и начнем с группы добавления/удаления элементов массива. В нее входят рассмотренные нами методы push/pop и shift/unshift и следующий метод – это splice.

splice

Метод splice() – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы. Его синтаксис такой:

Array.splice(index[, deleteCount, elem1, ..., elemN])

Он начинает с позиции index, удаляет deleteCount элементов и вставляет elem1, ..., elemN на их место. Возвращает массив из удалённых элементов. Этот метод проще всего понять, рассмотрев примеры. Начнем с удаления. Предположим, имеется массив:

let ar = ["Я", "смотрю", "этот", "обучающий", "урок"];

Удалим 3-й и 4-й элементы «этот» и «обучающий»:

ar.splice(2, 2);

мы здесь указали индекс элемента, с которого происходит удаление и число удаляемых элементов. Выведем результат в консоль:

console.log(ar);

Видим, в массиве остались строки «я», «смотрю», «урок».

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

let delElem = ar.splice(0, 3, "Это", "классный");

получаем массив ar:

«Это», «классный», «обучающий», «урок»

и массив delElem, состоящий из удаленных элементов. Этот пример также показывает, что метод splice возвращает массив из удаленных величин.

С помощью метода splice можно вставлять элементы, не удаляя существующие. Для этого аргумент deleteCount устанавливается в 0:

ar.splice(3, 0, "интересный");

Получим массив:

"Я", "смотрю", "этот", "интересный", "обучающий", "урок"

В этом и в других методах массива допускается использование отрицательного индекса. Он позволяет начать отсчёт элементов с конца:

ar.splice(-3, 3, "это", "обучающее", "видео");

Здесь удаляются последние 3 элемента и вместо них вставляются новые строчки.

slice

Метод slice имеет синтаксис:

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

возвращает массив, в который копирует элементы, начиная с индекса start и заканчивая индексом end-1. Например:

let ar = ["Я", "смотрю", "этот", "обучающий", "урок"];
 
let res1 = ar.slice(2, 4);   //этот, обучающий
let res2 = ar.slice(3);      //обучающий, урок
let res3 = ar.slice(-3);     //этот, обучающий, урок

В res1 копируются элементы с индексами 2 и 3, в res2 с индекса 3 и до конца массива, а в res3 от 3-го индекса с конца и до конца массива. Если вызвать просто

let copyArr = ar.slice();
console.log(copyArr);

то будет скопирован массив целиком. Этим часто пользуются, если нужно создать копию массива.

concat

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

Array.concat(arg1, arg2...)

Здесь arg1, arg2 могут быть как примитивными данными (строки, числа), так и массивами. Например:

let ar = [1, 2];
 
let res1 = ar.concat([3, 4]); // 1,2,3,4
console.log( res1 ); 
 
let res2 = ar.concat([3, 4], [5, 6]); // 1,2,3,4,5,6
console.log( res2 ); 
 
console.log( ar.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

По идее, этот метод работает и с объектами, вот так:

let obj = {name: "guest"};
let ar = [1, 2];
let res = ar.concat(obj);
 
console.log( res );

но здесь копируется лишь ссылка на объект, а не он сам.

forEach

Данный метод перебирает элементы массива и при этом позволяет с ними выполнить какие-либо действия. Имеет следующий синтаксис:

ar.forEach(function(item, index, array) {
  // ... делать что-то с item
});

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

let ar = ["Я", "смотрю", "этот", "обучающий", "урок"];
 
ar.forEach(function(item) {
         console.log(item);
});

Обратите внимание, что нам нет необходимости указывать все аргументы функции, достаточно лишь те, что необходимы. В реализацию метода forEach очень хорошо вписываются стрелочные функции, о которых мы говорили ранее, например так:

let dig = [1, 2, 3, 4, 5, 6, 7];
dig.forEach( (item) => console.log(item) );

или так для вывода только четных значений:

dig.forEach( (item, index) => {
         if(item % 2 == 0) console.log(`${item} с индексом ${index}`);
});

А вот так все нечетные элементы можно заменить на 1:

dig.forEach( (item, index, array) => {
         if(item % 2 != 0) array[index] = 1;
});

Далее мы рассмотрим группу методов для поиска элементов в массиве.

indexOf, lastIndexOf и includes

Данные методы имеют одинаковый синтаксис и делают по сути одно и то же с небольшими отличиями. Их синтаксис следующий:

  • ar.indexOf(item, from) ищет item, начиная с индекса from, и возвращает индекс, на котором был найден искомый элемент, в противном случае -1.
  • ar.lastIndexOf(item, from) – то же самое, но ищет справа налево.
  • ar.includes(item, from) – ищет item, начиная с индекса from, и возвращает true, если такой элемент был найден.

Например:

let ar = ["Я", "смотрю", "этот", "обучающий", "урок", 0, false, null];
 
let res1 = ar.indexOf("смотрю", 0);           // 1
let res2 = ar.lastIndexOf(null, 0);             // -1
let res3 = ar.includes(0, 3);                // true

Получим индекс, равный 1 для элемента «смотрю», -1 для null и true для нуля. Почему метод lastIndexOf вернул значение -1 (то есть, элемент null не найден), хотя он присутствует в массиве? Дело в том, что аргумент from – это индекс с которого мы просматриваем массив в обратном направлении. Так как здесь from=0, то мы идет назад с нулевого элемента, т.е. мы просматриваем только 1-й элемент. Чтобы просмотреть весь массив, этот индекс можно либо не указывать, либо указать индекс последнего элемента:

let res2 = ar.lastIndexOf(null);          // 7

Теперь значение null находится как надо. Также обратите внимание, что эти методы используют строгое сравнение ===. То есть, если мы ищем false, они находят именно false, а не ноль (числовой эквивалент false).

find и findIndex

Метод find позволяет найти элемент массива по какому-либо критерию (условию). Она имеет следующий синтаксис:

let result = ar.find(function(item, index, array) {
  // если true – возвращается текущий элемент и перебор прерывается
  // если все итерации оказались ложными, возвращается undefined
});

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

  • item – очередной элемент;
  • index – его индекс;
  • array – сам массив.

Предположим, у нас имеется массив объектов:

let cars = [
    {model: "toyota", price: 1000},
    {model: "opel", price: 800},
    {model: "reno", price: 1200}
];

Найдем в нем первую машину со стоимостью меньше 1000 единиц:

let res = cars.find(item => item.price < 1000);
console.log(res);

Результатом будет элемент opel. Так как на практике в JavaScript часто применяются массивы из объектов, то метод find бывает весьма полезным для поиска нужного элемента. Также обратите внимание, что в примере мы в find передаем стрелочной функции только один аргумент – item. Так тоже можно делать и это довольно типичная ситуация.

Метод ar.findIndex – по сути, то же самое, что и find, но возвращает индекс, по которому элемент был найден, и -1 в противном случае:

let res = cars.findIndex(item => item.price < 1000);

filter

Рассмотренные методы find и findIndex ищут первый подходящий элемент. Если же нужно найти все элементы по заданному критерию (условию), то следует использовать метод filter:

let results = ar.filter(function(item, index, array) {
  // если true – элемент добавляется к результату, и перебор продолжается
  // возвращается пустой массив в случае, если ничего не найдено
});

Перепишем наш пример:

let res = cars.filter(item => item.price <= 1000);

Получаем массив из двух найденных элементов.

Методы преобразования массива: map

Метод map довольно часто используется на практике и позволяет получить результаты обработки элементов массива. Его синтаксис похож на предыдущие функции:

let result = arr.map(function(item, index, array) {
  // возвращается новое значение вместо элемента
});

И может быть использован так:

let cars = ["toyota", "opel", "reno"];
 
let res = cars.map(function (item) {
    return item.length;
});

Мы здесь получаем массив длин строк массива cars.

sort

Данный метод сортирует массив по тому критерию, который указывается в ее необязательной callback-функции:

ar.sort(function(a, b) {
if (a > b) return 1; // если первое значение больше второго
if (a == b) return 0; // если равны
if (a < b) return -1; // если первое значение меньше второго
})

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

let dig = [4, 25, 2];
 
dig.sort();
console.log( dig );

И получим неожиданный результат: 2, 25, 4. Дело в том, что по умолчанию метод sort рассматривает значения элементов массива как строки и сортирует их в лексикографическом порядке. В результате, строка «2» < «4» и «25» < «4», отсюда и результат. Для указания другого критерия сортировки, мы должны записать свою callback-функцию:

dig.sort(function(a, b) {
    if(a > b) return 1;
    else if(a < b) return -1;
    else return 0;
});

Теперь сортировка с числами проходит так как нам нужно. Кстати, чтобы изменить направление сортировки (с возрастания на убывание), достаточно поменять знаки больше и меньше на противоположные. И второй момент: callback-функция не обязательно должна возвращать именно 1 и -1, можно вернуть любое положительное, если a>b и отрицательное при a<b. В частности, это позволяет переписать приведенный выше пример вот в такой краткой форме:

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

По аналогии можно формировать и более сложные алгоритмы сортировки самых разных типов данных: строк, чисел, объектов, булевых переменных и так далее.

reverse

Данный метод прост, он меняет порядок следования элементов на обратный. Например:

let dig = [4, 25, 2, 1];
dig.reverse();

Получим этот же массив со значениями: 1, 2, 25, 4.

split, join

Метод split применяется к строкам и позволяет разбить строку по указанному разделителю на массив строк. Например, мы в строке указываем email адреса, на которые хотим отправить письма:

let emailsTo = "alex12@m.ru; m2@m.com; pp@g.com; upr@g.ru";

Но в программе нам было бы удобнее оперировать массивами из этих адресов. Чтобы разбить строку по email мы вызовем для строки метод split и укажем разделитель «; »:

let arEmails = emailsTo.split("; ");
 
for( let email of arEmails)
    console.log( email );

Все, теперь можно использовать массив arEmails вместо строки с email адресами.

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

let arEmails = emailsTo.split("; ", 2);

то в выходном массиве будет только два первых элемента.

Метод join работает в точности наоборот: он из массива строк формирует единую строку, например:

let strEmails = arEmails.join(", ");
console.log( strEmails );

Получаем строку из email-адресов через запятую.

reduce и reduceRight

Если нам нужно перебрать массив – мы можем использовать forEach, for или for..of. Если нужно перебрать массив и вернуть данные для каждого элемента – мы используем map.

Методы reduce и reduceRight похожи на методы выше, но они немного сложнее и, как правило, используются для вычисления какого-нибудь единого значения на основе всего массива.

Синтаксис:

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

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

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

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

let digs = [1, -2, 100, 3, 9, 54];
 
let sum = digs.reduce((sum, current) => sum+current, 0);
console.log(sum);

Здесь значение sum при первом вызове будет равно 0, так как мы вторым аргументом метода reduce указали 0 – это начальное значение previousValue (то есть sum). Затем, на каждой итерации мы будем иметь ранее вычисленное значение sum, к которому прибавляем значение текущего элемента – current. Так и подсчитывается сумма.

А вот примеры вычисления произведения элементов массива:

let pr = digs.reduce((pr, current) => pr*current, 1);
console.log(pr);

Здесь мы уже указываем начальное значение 1, иначе бы все произведение было бы равно нулю.

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

let digs = [];
let pr = digs.reduce((pr, current) => pr*current);

Поэтому, лучше использовать начальное значение.

Метод reduceRight работает аналогично, но проходит по массиву справа налево.

Array.isArray

Массивы не образуют отдельный тип языка. Они основаны на объектах. Поэтому typeof не может отличить простой объект от массива:

console.log(typeof {}); // object
console.log (typeof []); // тоже object

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

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

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

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

push(...items)

добавляет элементы в конец

pop()

извлекает элемент с конца

shift()

извлекает элемент с начала

unshift(...items)

добавляет элементы в начало

splice(pos, deleteCount, ...items)

начиная с индекса pos, удаляет deleteCount элементов и вставляет items

slice(start, end)

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

concat(...items)

возвращает новый массив: копирует все члены текущего массива и добавляет к нему items (если какой-то из items является массивом, тогда берутся его элементы)

Для поиска среди элементов

indexOf/lastIndexOf(item, pos)

ищет item, начиная с позиции pos, и возвращает его индекс или -1, если ничего не найдено

includes(value)

возвращает true, если в массиве имеется элемент value, в противном случае false

find/filter(func)

фильтрует элементы через функцию и отдаёт первое/все значения, при прохождении которых через функцию возвращается true

findIndex(func)

похож на find, но возвращает индекс вместо значения

Для перебора элементов

forEach(func)

вызывает func для каждого элемента. Ничего не возвращает

Для преобразования массива

map(func)

создаёт новый массив из результатов вызова func для каждого элемента

sort(func)

сортирует массив «на месте», а потом возвращает его

reverse()

«на месте» меняет порядок следования элементов на противоположный и возвращает изменённый массив

split/join

преобразует строку в массив и обратно

reduce(func, initial)

вычисляет одно значение на основе всего массива, вызывая func для каждого элемента и передавая промежуточный результат между вызовами

Видео по теме