Отказ от ответственности: это не раскопки ни в одной из фантастических библиотек подчеркивания или 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] }), {})