Хабрахабр

[Перевод] Эффективное использование методов массивов в JavaScript

Автор материала, перевод которого мы публикуем сегодня, говорит, что в последние несколько месяцев ему, при проверке пулл-реквестов, постоянно попадались одни и те же четыре недочёта, связанных с нерациональным использованием методов массивов в JavaScript. Для того чтобы таких недостатков кода, которые раньше появлялись и в его программах, стало меньше, он и написал эту статью.

Замена indexOf() на includes()

«Если вы ищите что-то в массиве, используйте метод indexOf()». Примерно такая рекомендация мне встретилась на одном из курсов, когда я изучал JavaScript. Рекомендация это вполне нормальная, ничего плохого о ней сказать нельзя.

Это значит, что если мы планируем использовать в программе этот индекс, метод indexof() отлично подходит для поиска элементов в массивах. На MDN можно узнать, что метод indexOf() возвращает первый индекс, по которому некий элемент может быть найден в массиве.

То есть, нас интересует не индекс этого элемента, если он есть в массиве, а сам факт его наличия или отсутствия. А что если нам всего лишь нужно узнать, есть ли некий элемент в массиве или нет? В подобных случаях я рекомендую пользоваться не методом indexOf(), а методом includes(), который возвращает логическое значение. При таком подходе нас вполне устроит команда, которая возвращает true или false. Рассмотрим пример:

'use strict'; const characters = [ 'ironman', 'black_widow', 'hulk', 'captain_america', 'hulk', 'thor',
]; console.log(characters.indexOf('hulk'));
// 2
console.log(characters.indexOf('batman'));
// -1 console.log(characters.includes('hulk'));
// true
console.log(characters.includes('batman'));
// false

Использование метода find() вместо метода filter()

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

Например, такое может произойти при попытке отфильтровать элементы массива на основе некоего уникального идентификатора. Как быть, если мы знаем, что после фильтрации массива останется всего один элемент? Если нас интересует элемент массива с уникальным значением, то работать мы собираемся с единственным значением, а для представления такого значения массив не нужен. В такой ситуации я не советовал бы пользоваться методом filter(), так как тот массив, который он сформирует, будет содержать всего один элемент.

Более того, представим, что в массиве имеются сотни элементов, удовлетворяющих заданному условию. Если говорить о производительности метода filter(), то окажется, что ему, для формирования списка из элементов, соответствующих заданному при его вызове условию, придётся просмотреть весь массив. Это приведёт к тому, что результирующий массив окажется довольно большим.

При вызове ему передаётся коллбэк, описывающий условие, очень похожий на тот, который используется с методом filter(), но метод find() возвращает лишь первый элемент, соответствующий условию. Для того чтобы избежать попадания в подобные ситуации, я посоветовал бы использовать метод find(). Весь массив ему, в итоге, просматривать не приходится. При этом данный метод останавливает работу сразу после того, как найдёт такой элемент.

'use strict'; const characters = [ , { id: 2, name: 'black_widow' }, { id: 3, name: 'captain_america' }, { id: 4, name: 'captain_america' },
]; function getCharacter(name) { return character => character.name === name;
} console.log(characters.filter(getCharacter('captain_america')));
// [
// { id: 3, name: 'captain_america' },
// { id: 4, name: 'captain_america' },
// ] console.log(characters.find(getCharacter('captain_america')));
// { id: 3, name: 'captain_america' }

Замена метода find() на метод some()

Должен признать, ту оплошность, которую мы сейчас обсудим, я совершал много раз. Потом мне посоветовали заглянуть на MDN и посмотреть, как улучшить то, что я делал нерационально. Если в двух словах, то это очень похоже на то, что мы только что рассматривали, говоря о методах indexOf() и includes().

Можно ли назвать метод find() наиболее удачным решением в том случае, если нам надо узнать, содержит ли массив некое значение или нет? В вышеописанном случае мы видели, что метод find(), в качестве аргумента, принимает коллбэк и возвращает элемент массива. Возможно — нет, так как этот метод возвращает значение элемента массива, а не логическое значение.

В подобной ситуации я порекомендовал бы воспользоваться методом some(), который возвращает логическое значение.

'use strict'; const characters = [ { id: 1, name: 'ironman', env: 'marvel' }, { id: 2, name: 'black_widow', env: 'marvel' }, { id: 3, name: 'wonder_woman', env: 'dc_comics' },
]; function hasCharacterFrom(env) { return character => character.env === env;
} console.log(characters.find(hasCharacterFrom('marvel')));
// { id: 1, name: 'ironman', env: 'marvel' } console.log(characters.some(hasCharacterFrom('marvel')));
// true

Использование метода reduce() вместо комбинации методов filter() и map()

Стоит сказать, что метод reduce() нельзя отнести к простым для понимания. Однако, если то, что можно сделать с его помощью, делается в два приёма, с использованием методов filter() и map(), объединённых в цепочку, возникает такое ощущение, что что-то в таком подходе неправильно.

Первый проход, выполняемый методом filter(), предусматривает просмотр всего массива и создание нового, отфильтрованного массива. Я говорю о том, что при таком подходе массив приходится просматривать дважды. Как результат, для того, чтобы выйти на готовый массив, используют два метода. После второго прохода, выполняемого методом map(), опять же, создаётся новый массив, который содержит результаты преобразования элементов массива, полученного после работы метода filter(). У каждого метода есть собственный коллбэк, при этом в ходе выполнения такой вот операции с использованием метода filter() создаётся массив, с которым мы работать уже не сможем.

Результат будет таким же самым, а код получится лучше. Для того, чтобы снизить нагрузку на систему, создаваемую использованием двух методов и повысить производительность программ, в подобных случаях я посоветовал бы использовать метод reduce(). Аккумулятором может быть числовая переменная, хранящая, скажем, сумму элементов массива, это может быть объект, строка или массив, в которых можно накапливать нужные нам элементы. Этот метод позволяет фильтровать интересующие нас элементы и добавлять их в аккумулятор.

В следующем примере мы фильтруем элементы массива, являющиеся объектами, по значению поля env, и выполняем их преобразование. В нашем случае, так как речь идёт об использовании метода map(), я посоветовал бы использовать метод reduce() с массивом в качестве аккумулятора.

'use strict'; const characters = [ { name: 'ironman', env: 'marvel' }, { name: 'black_widow', env: 'marvel' }, { name: 'wonder_woman', env: 'dc_comics' },
]; console.log( characters .filter(character => character.env === 'marvel') .map(character => Object.assign({}, character, { alsoSeenIn: ['Avengers'] }))
);
// [
// { name: 'ironman', env: 'marvel', alsoSeenIn: ['Avengers'] },
// { name: 'black_widow', env: 'marvel', alsoSeenIn: ['Avengers'] }
// ] console.log( characters .reduce((acc, character) => { return character.env === 'marvel' ? acc.concat(Object.assign({}, character, { alsoSeenIn: ['Avengers'] })) : acc; }, [])
)
// [
// { name: 'ironman', env: 'marvel', alsoSeenIn: ['Avengers'] },
// { name: 'black_widow', env: 'marvel', alsoSeenIn: ['Avengers'] }
// ]

Итоги

В этом материале мы рассмотрели некоторые подходы к эффективному использованию методов массивов при решении различных задач. Полагаем, идеи, на которых основаны рекомендации, данные автором этой статьи, могут помочь в улучшении JS-кода и в других ситуациях.

Уважаемые читатели! Доводилось ли вам встречаться с примерами нерационального использования механизмов JavaScript?

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»