Как узнать, есть ли элемент в массиве?
Есть массив: [‘dog’, ‘cat’, ‘hamster’, ‘bird’, ‘fish’] , нужно вывести в консоль true , если массив содержит dog и false — если такой элемент отсутствует, но насколько я понимаю доступ к элементу в массиве проходит по номеру array[..] , каким образом можно проверить наличие того или иного элемента?
Отслеживать
13.7k 12 12 золотых знаков 43 43 серебряных знака 75 75 бронзовых знаков
задан 10 ноя 2015 в 22:21
353 2 2 золотых знака 4 4 серебряных знака 8 8 бронзовых знаков
– user176262
10 ноя 2015 в 22:28
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Маленькая интродукция
Задача поиска элемента в массиве стоит перед всеми достаточно часто и хорошо бы расписать, как это можно сделать.
Ищут обычно в массиве элементы типа Number , String или Object . Естественно, самый быстрый способ поиска — по элементам типа Number , сравнение числа, даже очень большого, происходит очень быстро, гораздо проще проверить один элемент, чем лексиграфически сравнивать строки, а с объектами вообще другая история. Если мы ищем именно тот объект, который мы добавили в массив, то есть сравниваем ссылки — это так же быстро, как и сравнивать числа, а вот если же надо искать по свойствам объекта, то это может весьма и весьма затянуться. В особо сложных случаях, советую составлять какой-нибудь хэш объекта и строить отдельным массив-карту, в которой уже спокойно искать всё, что надо найти.
Разберем 6 способов сделать это на нативном JS разной новизны и 3 способа с их разбором на популярных фреймворках: jQuery, underscore и lodash.
Часть первая, нативная, в стиле аллегро
Для начала надо пройтись по родным возможностям языка и посмотреть, что можно сделать самим.
Поиск в лоб
Попробуем просто идти по элементам массива, пока мы не встретим то, что нам нужно. Как всегда самое простое решение является в среднем самым быстрым.
function contains(arr, elem) < for (var i = 0; i < arr.length; i++) < if (arr[i] === elem) < return true; >> return false; >
Работает везде. Сравнивает строго, с помощью === . Легко можно заменить на == , бывает полезно, когда элементы массива разных типов, но может замедлить поиск. Его можно и модифицировать, добавив возможность начинать поиск элемента с конца. Шикарно ищет цифры, строки. Немного расширив, можно добавить возможность поиска элемента по своему условию (это поможет нам искать по свойствам объекта или, например, первый элемент, который больше 100500):
function contains(arr, pred) < for (var i = 0; i < arr.length; i++) < if (typeof pred == 'function' && pred(arr[i], i, arr) || arr[i] === elem) < return true; >> return false; >
Array.prototype.indexOf()
Array.prototype.indexOf(searchElement[, fromIndex = 0]) — старый добрый метод, заставляющий всех мучиться со своей -1 в случае, когда элемента нет.
function contains(arr, elem)
function contains(arr, elem, from)
Array.prototype.lastIndexOf()
Array.prototype.lastIndexOf(searchElement[, fromIndex = arr.length — 1]) — справедливости ради надо рассказать и про него. Работает полностью аналогично Array.prototype.indexOf() , но только полностью наоборот (поиск идет в обратном порядке и fromIndex изначально отсчитывается с конца). Заменил конструкцию ret != -1 на !!~ret ради забавы.
function contains(arr, elem, from)
Array.prototype.find()
Array.prototype.find(callback[, thisArg]) — модный стильный и молодежный ES6, со всеми вытекающими:
function contains(arr, elem) < return arr.find((i) =>i === elem) != -1; >
Возвращает элемент или -1 , если ничего не найдено. Ищет с помощью callback(elem, index, arr) , то есть, если эта функция вернет true , то это именно тот самый, искомый элемент. Конечно, эту функцию можно задавать самому, поэтому метод универсален.
Array.prototype.findIndex()
Array.prototype.findIndex(callback[, thisArg]) — полностью аналогичный предыдущему метод, за исключением того, что функция возвращает не элемент, а индекс. Забавы ради сделаю её с возможностью передать свою функцию:
function contains(arr, pred) < var f = typeof pred == 'function' ? pred : ( i =>i === pred ); return arr.findIndex(f) != -1; >
Array.prototype.includes()
Array.prototype.includes(searchElement[, fromIndex]) — а это уже ES7, с ещё пока оочень сырой поддержкой. Наконец-то у нас будет специальный метод, чтобы узнать, есть ли элемент в массиве! Поздравляю!
arr.includes(elem);
Это всё, что нужно, чтобы найти элемент. Аргументы у этой функции полностью аналогичны Array.prototype.indexOf() . А вот вернет он true в случае успеха и false в обратном. Естественно искать по свойствам объектов нельзя, для этого есть Array.prototype.find() . Должен быть самым быстрым, но. Возможно, что он и станет со временем самым быстрым.
Часть вторая, со вниманием, но чужая и в стиле сонаты
Теперь, наконец, можно поговорить об этой же теме, но в контексте парочки фреймворков! Говорят, что всегда хорошо посмотреть сначала, как делают другие, перед тем, как начнешь делать это сам. Может это откроет нам глаза на что-нибудь интересное!
jQuery
jQuery.inArray(value, array [, fromIndex ]) — между прочим весьма быстрый метод, по тестам.
Использует внутри строгое равенство === и возвращает -1 , если ничего не нашел, а если все таки нашёл, то вернет его индекс. Для удобства и одинаковости обернем её в функцию:
function contains(arr, elem)
А теперь поговорим, как она работает. Вот, что она представляет из себя в версии 2.1.3 :
inArray: function( elem, arr, i )
Где indexOf это вот это:
// Use a stripped-down indexOf as it's faster than native // http://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) < var i = 0, len = list.length; for ( ; i < len; i++ ) < if ( list[i] === elem ) < return i; >> return -1; >
Забавный комментарий говорит, что так быстрее, чем родной Array.prototype.indexOf() (могу предположить, что из-за отсутствия всех проверок) и предлагает посмотреть тесты производительности.
По сути — это самый первый способ из первой части.
Underscore
_.contains(list, value) — вот такой метод предлагает нам популярная библиотека для работы с коллекциями. То же самое, что и _.include(list, value) .
Использует === для сравнения. Вернёт true , если в list содержится элемент, который мы ищем. Если list является массивом, будет вызван метод indexOf.
_.contains = _.include = function(obj, target) < if (obj == null) return false; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; return any(obj, function(value) < return value === target; >); >;
Где nativeIndexOf — штука, которая говорит, что Array.prototype.indexOf() существует, а obj.indexOf === nativeIndexOf говорит, что list — массив. Теперь понятно, почему этот метод медленнее, чем jQuery.inArray() , просто обертка над Array.prototype.indexOf() . Ничего интересного.
Lodash
_.includes(collection, target, [fromIndex=0]) — вот последняя надежда на новые мысли, от второй знаменитейшей библиотеки для работы с коллекциями. То же самое, что _.contains() и _.include() .
Возвращает true , если содержит и false если нет. fromIndex индекс элемента, с которого начинаем поиск.
function includes(collection, target, fromIndex, guard) < var length = collection ? getLength(collection) : 0; if (!isLength(length)) < collection = values(collection); length = collection.length; >if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) < fromIndex = 0; >else < fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0); >return (typeof collection == 'string' || !isArray(collection) && isString(collection)) ? (fromIndex -1) : (!!length && getIndexOf(collection, target, fromIndex) > -1); >
guard — служебный аргумент. Сначала находится длина коллекции, выбирается fromIndex , а потом. нет, не Array.prototype.indexOf() ! Для поиска в строке используется String.prototype.indexOf() , а мы идем дальше в _.getIndexOf() и в результате попадём в ло-дашскую имплементацию indexOf() :
function indexOf(array, value, fromIndex) < var length = array ? array.length : 0; if (!length) < return -1; >if (typeof fromIndex == 'number') < fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex; >else if (fromIndex) < var index = binaryIndex(array, value); if (index < length && (value === value ? (value === array[index]) : (array[index] !== array[index]))) < return index; >return -1; > return baseIndexOf(array, value, fromIndex || 0); >
Она интересна тем, что fromIndex может принимать значения как Number , так и Boolean и если это всё таки значение булевого типа и оно равно true , то функция будет использовать бинарный поиск по массиву! Прикольно. Иначе же выполнится indexOf() попроще:
function baseIndexOf(array, value, fromIndex) < if (value !== value) < return indexOfNaN(array, fromIndex); >var index = fromIndex - 1, length = array.length; while (++index < length) < if (array[index] === value) < return index; >> return -1; >
Интересный ход для случая, когда мы ищем NaN (напомню, что из-за досадной ошибки он не равен сам себе). Выполнится indexOfNaN() , и действительно ни один из всех способов описанных ранее не смог бы найти NaN , кроме тех, естественно, где мы могли сами указать функцию для отбора элементов.
Можно предложить просто обернуть _.indexOf() для поиска элемента:
function contains(arr, elem, fromIndex)
Где fromIndex будет либо индексом откуда начинаем искать, либо true , если мы знаем, что arr отсортирован.
Заключение, хотя и в стиле интермеццо
И да, все эти варианты имеют смысл, только если момент с поиском данных част в вашем алгоритме или поиск происходит на очень больших данных. Вот приведу ниже несколько тестов на поиск элементов типа Number и String в массивах длинной 1000000 (миллион) элементов для трех случаев, когда элемент находится вначале массива, в середине (можно считать за среднюю по палете ситуацию) и в конце (можно считать за время поиска отсутствующего элемента, кроме метода с Array.prototype.lastIndexOf() ).
- Number , 1000000 элементов, искомый элемент в начале;
- Number , 1000000 элементов, искомый элемент в середине;
- Number , 1000000 элементов, искомый элемент в конце;
- String , 1000000 элементов, искомый элемент в начале;
- String , 1000000 элементов, искомый элемент в середине;
- String , 1000000 элементов, искомый элемент в конце.
Результаты тестов могут сильно зависеть от версии браузера, да и от самих браузеров, как этого избежать не знаю, но рекомендую протестить на нескольких.
Видно, что в среднем везде выигрывает первый способ из первой части (написанный собственноручно), а второе место обычно делят lоdash и Array.prototype.includes() .
Да, прошу заметить, что если взбредёт в голову искать NaN , то это может не получиться почти во всех методах, так как NaN !== NaN .
Проверить наличие элемента в массиве / объекте
Проверить наличие элемента в массиве можно через метод indexOf() , который возвращает позицию элемента в массиве. Если элемент не найден, то метод возвращает -1 .
let array = ['JavaScript', 'jQuery']; if (array.indexOf('JavaScript') !== -1) < // Выполнение кода, если элемент в массиве найден >
Проверить наличие объекта в объекте можно через код ниже:
let frameworks = < vuejs: true >; if ("vuejs" in frameworks) < // элемент есть в объекте >
Также есть вариант ниже, но его не рекомендуется использовать, т.к. если значения будет указано в undefined , то код его не найдёт:
// не рекомендуется использовать, код приведён в качестве примера let frameworks = < vuejs: undefined >; if (frameworks.vuejs !== undefined) < // элемент не найден, хотя он есть в объекте >
Обновлено: 03 апреля 2023
Комментарии
Авторизуйтесь, чтобы добавлять комментарии
- Создание массива
- Вывести элементы массива
- Добавить или удалить элемент в массиве
- Проверить наличие элемента в массиве / объекте
- Разбить строку на массив
- Преобразовать массив в строку
- Случайный элемент массива
- Количество элементов в массиве и объекте
- Выполнить функцию для каждого элемента массива
- Объединить массивы
- Удалить пустые элементы в массиве
Как проверить есть ли элемент в массиве js
Для этого в JS существует метод includes() , который работает на массивах. Метод includes() принимает два аргумента, один из которых обязательный. Это искомый элемент. После выполнения метод возвращает true или false в зависимости от того, есть элемент в массиве или нет. Ниже я привёл пример использования данного метода:
const collection = ['something', 'hexlet', 'something else']; const isHexletInCollection = collection.includes('hexlet'); console.log(isHexletInCollection); // => true
08 сентября 2022
Для поиска элемента в массиве можно использовать метод indexOf() , если элемент не найден, он возвращает -1 , если найден — индекс элемента:
const collection = ['something', 'hexlet', 'something else']; const isHexletInCollection = arr.indexOf('hexlet') != -1; console.log(isHexletInCollection); // => true
18 октября 2022
Для того, чтобы проверить, есть ли элемент в массиве, могут помочь не только стандартные методы, о которых писали выше. К примеру, есть библиотека Lodash, в которой присутствуют методы для поиска элементов в массиве.
Метод _.indexOf()
Этот метод устроен достаточно хитро, его можно использовать в двух вариантах:
- Первый вариант использования:
const numbers = [1, 2, 1, 2]; const findNumber = 2; const findIndex = _.indexOf(numbers, findNumber); // => 1 //В переменной findIndex окажется число 1 (индекс искомого элемента). //Если число 2 бы не нашлось, что в переменную findIndex было бы присвоено -1.
Как видно из примера выше, метод нашёл тот элемент, который встретился первым. Но, что делать, если в данном массиве есть и другие элементы, которые равны искомому?
Здесь начинается самое интересное. Данный метод может найти элемент, который равен искомому, но после определенного индекса.
- Второй вариант использования:
const numbers = [1, 2, 1, 2]; const findNumber = 2; const indexAfter = 2; const findIndex = _.indexOf(numbers, findNumber, indexAfter); // => 3 // Ищем число 2 после индекса 2. В итоге в переменной оказалось число 3. //Почему? 3 - индекс искомого числа, который встретился после второго индекса.
Если вдруг вам нужно выполнить поиск элемента массива не слева направо, а наоборот, в библиотеке Lodash найдётся и такой метод.
Он работает ровно зеркально методу _.indexOf().
Метод _.lastIndexOf()
- Первый вариант использования:
const numbers = [1, 2, 1, 2]; const findNumber = 2; const findIndex = _.lastIndexOf(numbers, findNumber); // => 3
В переменную findIndex вернулся индекс совпадающего значения, но не от начала массива, а от его конца.
- Второй вариант использования:
const numbers = [1, 2, 1, 2]; const findNumber = 2; const indexAfter = 2; const findIndex = _.lastIndexOf(numbers, findNumber, indexAfter); // => 1
В переменную findIndex вернулся индекс совпадающего значения, после второго индекса (но опять же не от начала массива, а от его конца).
Методы массивов
Массивы предоставляют множество методов. Чтобы было проще, в этой главе они разбиты на группы.
Добавление/удаление элементов
Мы уже знаем методы, которые добавляют и удаляют элементы из начала или конца:
- arr.push(. items) – добавляет элементы в конец,
- arr.pop() – извлекает элемент из конца,
- arr.shift() – извлекает элемент из начала,
- arr.unshift(. items) – добавляет элементы в начало.
splice
Как удалить элемент из массива?
Так как массивы – это объекты, то можно попробовать delete :
let arr = ["I", "go", "home"]; delete arr[1]; // удалить "go" alert( arr[1] ); // undefined // теперь arr = ["I", , "home"]; alert( arr.length ); // 3
Элемент был удалён, но в массиве всё ещё три элемента, мы можем увидеть, что arr.length == 3 .
Это естественно, потому что delete obj.key удаляет значение по ключу key . Это всё, что он делает. Хорошо для объектов. Но для массивов мы обычно хотим, чтобы оставшиеся элементы сдвинулись и заняли освободившееся место. Мы ждём, что массив станет короче.
Поэтому нужно использовать специальные методы.
Метод arr.splice – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы.
arr.splice(start[, deleteCount, elem1, . elemN])
Он изменяет arr начиная с индекса start : удаляет deleteCount элементов и затем вставляет elem1, . elemN на их место. Возвращает массив из удалённых элементов.
Этот метод легко понять, рассмотрев примеры.
Начнём с удаления:
let arr = ["Я", "изучаю", "JavaScript"]; arr.splice(1, 1); // начиная с индекса 1, удалить 1 элемент alert( arr ); // осталось ["Я", "JavaScript"]
Легко, правда? Начиная с индекса 1 , он убрал 1 элемент.
В следующем примере мы удалим 3 элемента и заменим их двумя другими.
let arr = ["Я", "изучаю", "JavaScript", "прямо", "сейчас"]; // удалить 3 первых элемента и заменить их другими arr.splice(0, 3, "Давай", "танцевать"); alert( arr ) // теперь ["Давай", "танцевать", "прямо", "сейчас"]
Здесь видно, что splice возвращает массив из удалённых элементов:
let arr = ["Я", "изучаю", "JavaScript", "прямо", "сейчас"]; // удалить 2 первых элемента let removed = arr.splice(0, 2); alert( removed ); // "Я", "изучаю"
Метод splice также может вставлять элементы без удаления, для этого достаточно установить deleteCount в 0 :
let arr = ["Я", "изучаю", "JavaScript"]; // с индекса 2 // удалить 0 элементов // вставить "сложный", "язык" arr.splice(2, 0, "сложный", "язык"); alert( arr ); // "Я", "изучаю", "сложный", "язык", "JavaScript"
Отрицательные индексы разрешены
В этом и в других методах массива допускается использование отрицательных индексов. Они определяют позицию с конца массива, как тут:
let arr = [1, 2, 5]; // начиная с индекса -1 (перед последним элементом) // удалить 0 элементов, // затем вставить числа 3 и 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5
slice
Метод 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 (копирует с 1 до 3) alert( arr.slice(-2) ); // s,t (копирует с -2 до конца)
Можно вызвать slice без аргументов: arr.slice() создаёт копию arr . Это часто используют, чтобы создать копию массива для дальнейших преобразований, которые не должны менять исходный массив.
concat
Метод arr.concat создаёт новый массив, в который копирует данные из других массивов и дополнительные значения.
arr.concat(arg1, arg2. )
Он принимает любое количество аргументов, которые могут быть как массивами, так и простыми значениями.
В результате – новый массив, включающий в себя элементы из arr , затем arg1 , arg2 и так далее.
Если аргумент argN – массив, то копируются все его элементы. Иначе копируется сам аргумент.
let arr = [1, 2]; // создать массив из: arr и [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 // создать массив из: arr и [3,4] и [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // создать массив из: arr и [3,4], потом добавить значения 5 и 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
Обычно он копирует только элементы из массивов. Другие объекты, даже если они выглядят как массивы, добавляются как есть:
let arr = [1, 2]; let arrayLike = < 0: "что-то", length: 1 >; alert( arr.concat(arrayLike) ); // 1,2,[object Object]
…Но если массивоподобный объект имеет специальное свойство Symbol.isConcatSpreadable , то он обрабатывается как массив, с помощью concat : вместо него добавляются его элементы:
let arr = [1, 2]; let arrayLike = < 0: "что-то", 1: "ещё", [Symbol.isConcatSpreadable]: true, length: 2 >; alert( arr.concat(arrayLike) ); // 1,2,что-то,ещё
Перебор: forEach
Метод arr.forEach позволяет запускать функцию для каждого элемента массива.
arr.forEach(function(item, index, array) < // . делать что-то с item >);
Например, этот код выведет на экран каждый элемент массива:
// Вызов alert для каждого элемента ["Бильбо", "Гэндальф", "Назгул"].forEach(alert);
А этот вдобавок расскажет и о позиции элемента в целевом массиве:
["Бильбо", "Гэндальф", "Назгул"].forEach((item, index, array) => < alert(`У $индекс $ в $`); >);
Результат функции (если она что-то возвращает) отбрасывается и игнорируется.
Поиск в массиве
Теперь рассмотрим методы поиска в массиве.
indexOf/lastIndexOf и includes
У методов 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
Пожалуйста, обратите внимание, что методы используют строгое сравнение === . Таким образом, если мы ищем false , он находит именно false , а не ноль.
Если мы хотим проверить наличие элемента в массиве и нет необходимости знать его индекс, предпочтительно использовать arr.includes .
Метод arr.lastIndexOf похож на indexOf , но ищет справа налево.
let fruits = ['Яблоко', 'Апельсин', 'Яблоко'] alert( fruits.indexOf('Яблоко') ); // 0 (первый 'Яблоко') alert( fruits.lastIndexOf('Яблоко') ); // 2 (последний 'Яблоко')
Метод includes правильно обрабатывает NaN
Незначительная, но заслуживающая внимания особенность includes – он правильно обрабатывает NaN , в отличие от indexOf :
const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (неверно, должен быть 0) alert( arr.includes(NaN) );// true (верно)
Это связано с тем, что includes был добавлен в JavaScript гораздо позже и использует более современный алгоритм сравнения.
find и findIndex/findLastIndex
Представьте, что у нас есть массив объектов. Как нам найти объект с определённым условием?
Здесь пригодится метод arr.find.
let result = arr.find(function(item, index, array) < // если true - возвращается текущий элемент и перебор прерывается // если все итерации оказались ложными, возвращается undefined >);
Функция вызывается по очереди для каждого элемента массива:
- item – очередной элемент.
- index – его индекс.
- array – сам массив.
Если функция возвращает true , поиск прерывается и возвращается item . Если ничего не найдено, возвращается undefined .
Например, у нас есть массив пользователей, каждый из которых имеет поля id и name . Найдем пользователя с id == 1 :
let users = [ , , ]; let user = users.find(item => item.id == 1); alert(user.name); // Вася
В реальной жизни массивы объектов – обычное дело, поэтому метод find крайне полезен.
Обратите внимание, что в данном примере мы передаём find функцию item => item.id == 1 с одним аргументом. Это типично, другие аргументы этой функции используются редко.
У метода arr.findIndex такой же синтаксис, но он возвращает индекс, на котором был найден элемент, а не сам элемент. Значение -1 возвращается, если ничего не найдено.
Метод arr.findLastIndex похож на findIndex , но ищет справа налево, наподобие lastIndexOf .
let users = [ , , , ]; // Найти индекс первого Васи alert(users.findIndex(user => user.name == 'Вася')); // 0 // Найти индекс последнего Васи alert(users.findLastIndex(user => user.name == 'Вася')); // 3
filter
Метод find ищет один (первый) элемент, который заставит функцию вернуть true .
Если найденных элементов может быть много, можно использовать arr.filter(fn).
Синтаксис схож с find , но filter возвращает массив из всех подходящих элементов:
let results = arr.filter(function(item, index, array) < // если `true` -- элемент добавляется к results и перебор продолжается // возвращается пустой массив в случае, если ничего не найдено >);
let users = [ , , ]; // возвращает массив, состоящий из двух первых пользователей let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2
Преобразование массива
Перейдём к методам преобразования и упорядочения массива.
map
Метод arr.map является одним из наиболее полезных и часто используемых.
Он вызывает функцию для каждого элемента массива и возвращает массив результатов выполнения этой функции.
let result = arr.map(function(item, index, array) < // возвращается новое значение вместо элемента >);
Например, здесь мы преобразуем каждый элемент в его длину:
let lengths = ["Бильбо", "Гэндальф", "Назгул"].map(item => item.length); alert(lengths); // 6,8,6
sort(fn)
Вызов arr.sort() сортирует массив на месте, меняя в нём порядок элементов.
Он также возвращает отсортированный массив, но обычно возвращаемое значение игнорируется, так как изменяется сам arr .
let arr = [ 1, 2, 15 ]; // метод сортирует содержимое 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 (a == b) return 0; // если равны if (a < b) return -1; // если первое значение меньше второго >
Например, для сортировки чисел:
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 (неправильно) alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (правильно!)
reverse
Метод arr.reverse меняет порядок элементов в arr на обратный.
let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1
Он также возвращает массив arr с изменённым порядком элементов.
split и join
Ситуация из реальной жизни. Мы пишем приложение для обмена сообщениями, и посетитель вводит имена тех, кому его отправить, через запятую: Вася, Петя, Маша . Но нам-то гораздо удобнее работать с массивом имён, чем с одной строкой. Как его получить?
Метод str.split(delim) именно это и делает. Он разбивает строку на массив по заданному разделителю delim .
В примере ниже таким разделителем является строка из запятой и пробела.
let names = 'Вася, Петя, Маша'; let arr = names.split(', '); for (let name of arr) < alert( `Сообщение получат: $.` ); // Сообщение получат: Вася (и другие имена) >
У метода split есть необязательный второй числовой аргумент – ограничение на количество элементов в массиве. Если их больше, чем указано, то остаток массива будет отброшен. На практике это редко используется:
let arr = 'Вася, Петя, Маша, Саша'.split(', ', 2); alert(arr); // Вася, Петя
Разбивка по буквам
Вызов split(s) с пустым аргументом s разбил бы строку на массив букв:
let str = "тест"; alert( str.split('') ); // т,е,с,т
Вызов arr.join(glue) делает в точности противоположное split . Он создаёт строку из элементов arr , вставляя glue между ними.
let arr = ['Вася', 'Петя', 'Маша']; let str = arr.join(';'); // объединить массив в строку через ; alert( str ); // Вася;Петя;Маша
reduce/reduceRight
Когда нам нужно перебрать массив – мы можем использовать 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 , использует только два аргумента, этого обычно достаточно.
Разберём детально как это работает.
- При первом запуске sum равен initial (последний аргумент reduce ), то есть 0 , а current – первый элемент массива, равный 1 . Таким образом, результат функции равен 1 .
- При втором запуске sum = 1 , к нему мы добавляем второй элемент массива ( 2 ) и возвращаем.
- При третьем запуске sum = 3 , к которому мы добавляем следующий элемент, и так далее…
Поток вычислений получается такой:
Или в виде таблицы, где каждая строка показывает вызов функции на очередном элементе массива:
| sum | current | result | |
|---|---|---|---|
| первый вызов | 0 | 1 | 1 |
| второй вызов | 1 | 2 | 3 |
| третий вызов | 3 | 3 | 6 |
| четвёртый вызов | 6 | 4 | 10 |
| пятый вызов | 10 | 5 | 15 |
Здесь отчётливо видно, как результат предыдущего вызова передаётся в первый аргумент следующего.
Мы также можем опустить начальное значение:
let arr = [1, 2, 3, 4, 5]; // убрано начальное значение (нет 0 в конце) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15
Результат – точно такой же! Это потому, что при отсутствии initial в качестве первого значения берётся первый элемент массива, а перебор стартует со второго.
Таблица вычислений будет такая же за вычетом первой строки.
Но такое использование требует крайней осторожности. Если массив пуст, то вызов reduce без начального значения выдаст ошибку.
let arr = []; // Error: Reduce of empty array with no initial value // если бы существовало начальное значение, reduce вернул бы его для пустого массива. arr.reduce((sum, current) => sum + current);
Поэтому рекомендуется всегда указывать начальное значение.
Метод arr.reduceRight работает аналогично, но проходит по массиву справа налево.
Array.isArray
Массивы не образуют отдельный тип данных. Они основаны на объектах.
Поэтому typeof не может отличить простой объект от массива:
alert(typeof <>); // object alert(typeof []); // тоже object
…Но массивы используются настолько часто, что для этого придумали специальный метод: 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 -- необязательный последний аргумент
Значение параметра thisArg становится this для func .
Например, тут мы используем метод объекта army как фильтр, и thisArg передаёт ему контекст:
let army = < minAge: 18, maxAge: 27, canJoin(user) < return user.age >= this.minAge && user.age < this.maxAge; >>; let users = [ , , , ]; // найти пользователей, для которых army.canJoin возвращает 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 (не включая 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 для каждого элемента и передавая промежуточный результат между вызовами.
- Array.isArray(arr) проверяет, является ли arr массивом.
Пожалуйста, обратите внимание, что методы push , pop , shift , unshift , sort , reverse и splice изменяют исходный массив.
Эти методы – самые используемые, их достаточно в 99% случаев. Но существуют и другие:
-
arr.some(fn)/arr.every(fn) проверяет массив. Функция fn вызывается для каждого элемента массива аналогично map . Если какие-либо/все результаты вызовов являются true , то метод возвращает true , иначе false . Эти методы ведут себя примерно так же, как операторы || и && : если fn возвращает истинное значение, arr.some() немедленно возвращает true и останавливает перебор остальных элементов; если fn возвращает ложное значение, arr.every() немедленно возвращает false и также прекращает перебор остальных элементов. Мы можем использовать every для сравнения массивов:
function arraysEqual(arr1, arr2) < return arr1.length === arr2.length && arr1.every((value, index) =>value === arr2[index]); > alert( arraysEqual([1, 2], [1, 2])); // trueПолный список есть в справочнике MDN.
На первый взгляд может показаться, что существует очень много разных методов, которые довольно сложно запомнить. Но это гораздо проще, чем кажется.
Внимательно изучите шпаргалку, представленную выше, а затем, чтобы попрактиковаться, решите задачи, предложенные в данной главе. Так вы получите необходимый опыт в правильном использовании методов массива.
Всякий раз, когда вам будет необходимо что-то сделать с массивом, а вы не знаете, как это сделать – приходите сюда, смотрите на таблицу и ищите правильный метод. Примеры помогут вам всё сделать правильно, и вскоре вы быстро запомните методы без особых усилий.
Задачи
Переведите текст вида border-left-width в borderLeftWidth
важность: 5
Напишите функцию camelize(str) , которая преобразует строки вида «my-short-string» в «myShortString».
То есть дефисы удаляются, а все слова после них получают заглавную букву.
camelize("background-color") == 'backgroundColor'; camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition';P.S. Подсказка: используйте split , чтобы разбить строку на массив символов, потом переделайте всё как нужно и методом join соедините обратно.
function camelize(str) < return str .split('-') // разбивает 'my-long-word' на массив ['my', 'long', 'word'] .map( // Переводит в верхний регистр первые буквы всех элементом массива за исключением первого // превращает ['my', 'long', 'word'] в ['my', 'Long', 'Word'] (word, index) =>index == 0 ? word : word[0].toUpperCase() + word.slice(1) ) .join(''); // соединяет ['my', 'Long', 'Word'] в 'myLongWord' >