Отказ от ответственности: это не раскопки ни в одной из фантастических библиотек подчеркивания или lodash, ни в какой-либо другой служебной библиотеке. Это не призыв прекратить использование любого из них. И это не шаг для какой-то замены библиотеки. Скорее это предложение остановиться и подумать. Улучшает ли использование служебной библиотеки опыт конечного пользователя в данном проекте, или мы просто привыкаем к предыдущим проектам?
Как обсуждалось в Части I, может быть много причин для использования служебной библиотеки. Я надеюсь убедить вас, дорогой читатель, что руководствоваться такими соображениями не следует по простой привычке.
Мы начали использовать библиотеку lodash в качестве прокси для множества служебных библиотек и выбрали из нее несколько служебных функций Array, у которых был собственный аналог. Мы рассмотрели, как использовать эти конкретные функции в lodash, а затем реализовали их с помощью нативной встроенной копии.
На этот раз мы рассмотрим служебные функции, которые не имеют нативного аналога, но, тем не менее, просты в реализации. Опять же, цель здесь не в том, чтобы убедить вас не использовать выбранную вами библиотеку утилит, а в том, чтобы показать вам, что вам это не обязательно нужно. Давайте начнем.
Тривиальные нативные решения
_.компактный
_.compact отфильтровывает массив только по истинным значениям.
_.compact([0,1,false,2,'',3]) // [1,2,3]
это, пожалуй, самый простой полезный пример фильтра.
[0,1,false,2,'',3].filter(item => !!item) // [1,2,3]
Поскольку метод filter преобразует каждое возвращаемое значение в логическое значение, нам даже не нужно приведение !!
. Чтобы немного упростить задачу, мы можем сделать это с помощью другой служебной функции в lodash, которая также чрезвычайно тривиальна. Функция identity, которая просто возвращает переданный ей аргумент. Звучит не очень полезно, но у него есть несколько удивительных приложений, таких как это. Мы снова рассмотрим identity, когда дойдем до категории util lodash.
_.разница
_.difference фильтрует целевой массив только для тех значений, которых нет в исходном массиве.
_.difference([2, 1], [2, 3]) // => [1]
Здесь мы можем объединить простой filter
с методом массива includes
, который возвращает true
, если целевой элемент присутствует в массиве, и false в противном случае.
[2, 1].filter(item => ![2, 3].includes(item)) // [1]
_.differenceBy
_.differenceBy почти идентичен _.difference, за исключением того, что вместо проверки самого элемента он позволяет потребителю дать ему пользовательскую функцию, чтобы добраться до сути того, что сравнивается.
_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor) // => [1.2]
В этом случае Math.floor
вызывается для каждого элемента перед его сравнением. Это приводит к сравнению только целой числовой части каждого элемента, игнорируя дробную часть.
const iteratee = Math.floor [2.1, 1.2] .filter(item => ![2.3, 3.4].map(iteratee).includes(iteratee(item))) // [1.2]
Это немного сложнее, чем пример difference
. Все, что мы делаем по-другому, — это вызываем iteratee
для каждого элемента в целевом массиве перед проверкой его существования в исходном массиве, который также был сопоставлен с помощью функции iteratee
.
Это, вероятно, подходит для небольших массивов, но мы сопоставляем весь исходный массив с каждым элементом в целевом массиве. Мы можем немного оптимизировать это, сохранив копию сопоставленного исходного массива с самого начала.
const iteratee = Math.floor const mappedSource = [2.3, 3.4].map(iteratee) [2.1, 1.2] .filter(item => !mappedSource.includes(iteratee(item))) // [1.2]
_.уронить
_.drop отсекает от начала массива указанное количество элементов, по умолчанию равное 1.
_.drop([1,2,3,4,5]) // [2,3,4,5] _.drop([1,2,3,4,5], 2) // [3,4,5]
[].slice можно использовать точно так же, как _.drop. Просто передайте [].slice одно положительное целое число. Если аргументы [].slice не передаются, ничего не удаляется. Он эффективно выполняет неглубокое копирование.
[1,2,3,4,5].slice() // [1,2,3,4,5] [1,2,3,4,5].slice(2) // [3,4,5]
_.dropRight
Подобно _.drop, только _.dropRight срезается с конца массива.
_.dropRight([1,2,3,4,5]) // [1,2,3,4] _.dropRight([1,2,3,4,5], 2) // [1,2,3]
У [].slice есть интересная особенность: если вы передадите ему отрицательное целое число в качестве первого аргумента, он переместится назад от конца массива. Поэтому мы можем использовать его очень похоже на _.dropRight.
[1,2,3,4,5].slice(-1) // [1,2,3,4] [1,2,3,4,5].slice(-2) // [1,2,3]
_.первый/_.голова
Эти два псевдонима друг для друга, поэтому я просто буду ссылаться на head. _.head возвращает первый элемент массива
_.head([1,2,3,4,5]) // 1
Конечно, к массивам можно получить доступ по индексу, поэтому мы можем тривиально реализовать _.head.
[1,2,3,4,5][0] // 1
Мы могли бы также использовать деструктурирование.
const [head] = [1,2,3,4,5]; // head = 1
Это здорово, потому что нередко требуется «хвост» вместе с «головой». Мы можем использовать сбор остальной части массива с помощью оператора rest (`…`) для очень краткого шаблона.
const [head, ...tail] = [1,2,3,4,5]; // head = 1 // tail = [2,3,4,5]
_.сгладить
Как следует из названия, flatten выводит элементы из подмассивов на один уровень вверх.
_.flatten([1, [2, 3, 4], 5]) // => [1, 2, 3, 4, 5]
Мы можем комбинировать одну функцию синтаксиса с одним методом массива и добиться точно такого же эффекта. Мы распространяем массив, который хотим сгладить, в качестве аргументов в метод concat пустого массива.
[].concat(...[1, [2, 3, 4], 5]) // [1, 2, 3, 4, 5]
_.fromPairs
Этот метод возвращает объект, состоящий из пар ключ-значение.
_.fromPairs([['a', 1], ['b', 2]]) // => { 'a': 1, 'b': 2 }
Есть предложение добавить статический метод fromEntries
в класс Object
, чтобы сделать это нативно, но поскольку это еще не языковая функция, мы ее реализуем. Здесь мы меняем структуры данных — массив на объект — так что это отличная возможность использовать reduce
, где мы будем создавать наш объект по мере того, как мы перебираем наш массив.
[[a, 1], [b, 2]].reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {}) // { a: 1, b: 2 }
Для каждого элемента, который мы итерируем, мы создаем новый объект, который состоит из предыдущего объекта (...obj
) с первым элементом в массиве, который мы сейчас итерируем, в качестве ключа объекта ([key]
) и вторым элементом в качестве его значения (val
) .
_.исходный
_.initial берет срез исходного массива до последнего элемента, но не включает его.
_.initial([1,2,3,4,5]) // [1,2,3,4]
Мы видели, как использовать [].slice с одним параметром, когда рассматривали _.head. Этот первый параметр на самом деле является beginIndex, указывающим, с чего начинать срез. [].slice также принимает второй параметр. Второй параметр — endIndex. endIndex указывает [].slice, где остановиться, и является исключительным, то есть элемент с этим индексом не будет включен. Как и в случае с параметром beginIndex, отрицательный параметр endIndex будет ссылаться на конец исходного массива и идти в обратном порядке.
[1,2,3,4,5].slice(0, -1) // [1,2,3,4]
_.последний
_.last является инверсией _.first. Он вернет последний элемент в массиве.
_.last([1,2,3,4,5]) // 5
Мы можем получить последний элемент напрямую следующим образом:
[1,2,3,4,5].slice(-1)[0];
_.nth
_.nth вернет n-й элемент из массива. Отрицательные индексы возвращают n-й с конца.
_.nth([1,2,3,4,5], 2) // 3 _.nth([1,2,3,4,5], -2) // 4
Следуя аналогичным предыдущим шаблонам, мы можем использовать срез для достижения этой цели. Разница в том, что срез возвращает массив, который мы можем развернуть, если нужно, сославшись на индекс 0.
[1,2,3,4,5].slice(2,3) // [3][0] => 3 [1,2,3,4,5].slice(-3,-2) // [4][0] => 4
_.брать
_.take создает массив от 0 до n исходного массива.
_.take([1,2,3,4,5], 3) // [1,2,3]
Это тривиально достигается с помощью [].slice
[1,2,3,4,5].slice(0, 3) // [1,2,3]
_.takeRight
_.takeRight создает массив от n до arr.length включительно.
_.takeRight([1,2,3,4,5], 3) // [3,4,5]
Это еще более тривиально достигается с помощью slice.
[1,2,3,4,5].slice(-3) // [3,4,5]
_.союз
_.union([1,2], [2,3], [3,4]) // [1,2,3,4]
Объединение можно рассматривать как concat, за которым следует дедупликация. Мы уже видели concat и можем использовать Set, обеспечивающий уникальность, для дедупликации.
[...new Set([].concat([1,2], [2,3], [3,4]))] // [1,2,3,4]
_.uniq
_.uniq выполняет дедупликацию массива, сохраняя первое вхождение элемента.
_.uniq([1,2,3,4,1,2,3,5]) // [1,2,3,4,5]
Set – это структура данных, похожая на массивы, но обеспечивающая уникальность. Мы можем использовать эту функцию для автоматической дедупликации массива путем преобразования массива в Set и обратно в массив.
[...new Set([1,2,3,4,1,2,3,5])] // [1,2,3,4,5]
_.без
_.without принимает массив в качестве первого аргумента и любые значения, которые вы хотите исключить из этого массива, в качестве остальных аргументов. Он возвращает массив, аналогичный первому массиву, только «без» каких-либо элементов, соответствующих переданным аргументам.
_.uniq([1,2,3,4,5], 1, 3, 5); // [2,4]
Это не более чем фильтр, который можно использовать для выполнения той же задачи.
[1,2,3,4,5].filter(item => [1,3,5].includes(item))
_.zip
_.zip берет любое количество массивов и рекомбинирует их как массивы элементов по соответствующим индексам.
_.zip(['a', 'b'], [1, 2, 3], [true, false]); // => [['a', 1, true], ['b', 2, false], [undefined, 3, undefined]]
Нам нужно будет сделать это в два этапа. Сначала нам нужно найти самый большой из массивов, которые мы сжимаем вместе. Затем мы сопоставляем этот массив и возвращаем для каждого элемента массив элементов из всех массивов с соответствующим индексом.
const zip = (arrs) => arrs .reduce((longest, next) => next.length > longest.length ? next : longest, []) .map((_, i) => arrs.map((arr) => arr[i])) } zip([['a', 'b'], [1, 2, 3], [true, false]]) // [['a', 1, true], ['b', 2, false], [undefined, 3, undefined]]
_.zipОбъект
_.zipObject принимает два массива, обрабатывая значения в первом массиве как набор ключей и значения во втором массиве как набор значений, и создает объект, объединяющий ключи со значениями.
_.zipObject(['a', 'b'], [1, 2]); // => { 'a': 1, 'b': 2 }
Есть много способов приблизиться к этому, но поскольку мы изменяем структуры данных, мы собираемся обратиться к reduce
, чтобы помочь нам. Мы перебираем первый массив и создаем объект с reduce
, используя индекс итерации для доступа к соответствующему значению во втором массиве.
zipObject = (arr1, arr2) => arr1.reduce((obj, key, i) => ({ …obj, [key]: arr2[i] }), {})