chunk

将一个数组分成多个小数组

const chunk = (arr, size) =>
  Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
    arr.slice(i * size, i * size + size)
  );

chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]

chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]

思路:根据指定的大小将数组切割成多个小数组,使用 ES6 的 Array.from()方法,创建一个长度为切割后数组个数的新数组,使用 slice()方法将原数组按照切割后的大小进行分割,然后将每个小数组存储在新数组中返回。

compact

去除数组中的假值(false、null、0、""、undefined、NaN)

const compact = arr => arr.filter(Boolean);

compact([0, 1, false, 2, '', 3]);
// => [1, 2, 3]

思路:使用 filter()方法,过滤出数组中的真值,Boolean()函数将所有假值转化为 false,所有真值转化为 true。

concat

合并多个数组

const concat = (...args) => [].concat(...args);

const array = [1];
const other = concat(array, 2, [3], [[4]]);

console.log(other);
// => [1, 2, 3, [4]]

console.log(array);
// => [1]

思路:使用 ES6 的扩展运算符(...)将传入的参数转化为数组,然后使用 concat()方法将所有数组合并成一个新数组并返回。

difference

返回一个数组,包含在第一个数组中但不在其他数组中的元素

// // 第1种实现
// const difference = (arr, ...args) => arr.filter((item) => !args.flat().includes(item));
// 第2种实现
const difference = (arr, ...args) =>
  arr.filter(item => args.every(arg => !arg.includes(item)));

difference([3, 2, 1], [4, 2]);
// => [3, 1]

思路:使用 filter()方法遍历第一个数组,使用 every()方法遍历其他数组,将第一个数组中不包含在其他数组中的元素过滤出来,返回一个新数组。

differenceBy

与 difference 类似,但是可以指定一个函数对比数组中的元素

const differenceBy = (array, values, iteratee) => {
  const fn = typeof iteratee === 'function' ? iteratee : item => item[iteratee];
  const valuesSet = new Set(values.map(fn));
  return array.filter(item => !valuesSet.has(fn(item)));
};

differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
// => [3.1, 1.3]

// The `property` iteratee shorthand.
differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], 'x');
// => [{ 'x': 2 }]

思路:先判断第三个参数是否为函数,如果是则使用函数对比数组中的元素进行差异筛选,否则直接使用 lodash 的差异筛选函数来实现。

differenceWith

从第一个数组中过滤出第二个数组中没有的元素,使用一个自定义比较函数进行比较。

const differenceWith = (array, values, comparator) =>
  array.filter(item => !values.some(value => comparator(item, value)));

const objects = [
  { x: 1, y: 2 },
  { x: 2, y: 1 },
];

_.differenceWith(objects, [{ x: 1, y: 2 }], _.isEqual);
// => [{ 'x': 2, 'y': 1 }]

思路:利用高阶函数 filter 和 some 对两个数组进行比较,返回第一个数组中不包含在第二个数组中的元素。

drop

返回一个新数组,去掉原数组中的前 n 个元素

const drop = (arr, n = 1) => arr.slice(n);

drop([1, 2, 3]);
// => [2, 3]

drop([1, 2, 3], 2);
// => [3]

drop([1, 2, 3], 5);
// => []

drop([1, 2, 3], 0);
// => [1, 2, 3]

思路:使用 slice()方法将原数组中的前 n 个元素删除并返回。

dropRight

返回一个新数组,去掉原数组中的后 n 个元素

const dropRight = (arr, n = 1) =>
  n >= arr.length ? [] : arr.slice(0, arr.length - n);

dropRight([1, 2, 3]);
// => [1, 2]

dropRight([1, 2, 3], 2);
// => [1]

dropRight([1, 2, 3], 5);
// => []

dropRight([1, 2, 3], 0);
// => [1, 2, 3]

思路:根据 n 的值,通过数组的 slice 方法获取新的数组,从而实现删除末尾元素的操作。

dropRightWhile

返回一个新数组,去掉原数组中从最后一个符合条件的元素到结尾之间的元素

const dropRightWhile = (array, iteratee) => {
  let right = array.length - 1;
  if (typeof iteratee === 'function') {
    while (iteratee(array[right])) {
      right--;
    }
  }
  if (typeof iteratee === 'object' && !Array.isArray(iteratee)) {
    const entries = Object.entries(iteratee);
    while (entries.every(([key, value]) => array[right][key] === value)) {
      right--;
    }
  }
  if (Array.isArray(iteratee) && iteratee.length === 2) {
    const [key, value] = iteratee;
    while (array[right][key] === value) {
      right--;
    }
  }
  return array.slice(0, right + 1);
};

const users = [
  { user: 'barney', active: true },
  { user: 'fred', active: false },
  { user: 'pebbles', active: false },
];

dropRightWhile(users, o => !o.active);
// => objects for ['barney']

// The `matches` iteratee shorthand.
dropRightWhile(users, { user: 'pebbles', active: false });
// => objects for ['barney', 'fred']

// The `matchesProperty` iteratee shorthand.
dropRightWhile(users, ['active', false]);
// => objects for ['barney']

// The `property` iteratee shorthand.
dropRightWhile(users, 'active');
// => objects for ['barney', 'fred', 'pebbles']

思路:该函数实现了一个从数组末尾开始遍历,当元素满足传入的迭代器条件时,将该元素从数组中删除并返回删除后的新数组的函数。迭代器可以是函数、对象或数组。

fill

用指定的值填充数组

const fill = (arr, value, start = 0, end = arr.length) =>
  arr.fill(value, start, end);

const array = [1, 2, 3];

fill(array, 'a');
console.log(array);
// => ['a', 'a', 'a']

fill(Array(3), 2);
// => [2, 2, 2]

fill([4, 6, 8, 10], '*', 1, 3);
// => [4, '*', '*', 10]

思路:使用 fill()方法将数组的指定位置开始到指定位置结束的元素替换成指定的值,并返回修改后的数组。

findIndex

返回第一个符合条件的元素的下标

const findIndex = (arr, fn) => arr.findIndex(fn);

const users = [
  { user: 'barney', active: false },
  { user: 'fred', active: false },
  { user: 'pebbles', active: true },
];

findIndex(users, o => o.user === 'barney');
// => 0

// The `matches` iteratee shorthand.
findIndex(users, { user: 'fred', active: false });
// => 1

// The `matchesProperty` iteratee shorthand.
findIndex(users, ['active', false]);
// => 0

// The `property` iteratee shorthand.
findIndex(users, 'active');
// => 2

思路:使用 findIndex()方法查找符合条件的元素在数组中的下标,如果没有找到则返回-1。

findLastIndex

返回最后一个符合条件的元素的下标

const findLastIndex = (arr, predicate) => {
  if (typeof predicate === 'function') {
    for (let i = arr.length - 1; i >= 0; i--) {
      if (predicate(arr[i], i, arr)) {
        return i;
      }
    }
  } else if (Array.isArray(predicate)) {
    const [key, value] = predicate;
    for (let i = arr.length - 1; i >= 0; i--) {
      if (arr[i][key] === value) {
        return i;
      }
    }
  } else if (typeof predicate === 'object') {
    for (let i = arr.length - 1; i >= 0; i--) {
      const keys = Object.keys(predicate);
      const match = keys.every(key => predicate[key] === arr[i][key]);
      if (match) {
        return i;
      }
    }
  } else {
    for (let i = arr.length - 1; i >= 0; i--) {
      if (arr[i] && arr[i][predicate]) {
        return i;
      }
    }
  }
  return -1;
};

const users = [
  { user: 'barney', active: true },
  { user: 'fred', active: false },
  { user: 'pebbles', active: false },
];

findLastIndex(users, o => o.user === 'pebbles');
// => 2

// The `matches` iteratee shorthand.
findLastIndex(users, { user: 'barney', active: true });
// => 0

// The `matchesProperty` iteratee shorthand.
findLastIndex(users, ['active', false]);
// => 2

// The `property` iteratee shorthand.
findLastIndex(users, 'active');
// => 0

思路:使用 findLastIndex()方法,返回数组中满足提供的测试函数条件的最后一个元素的索引。若没有找到对应元素,则返回 -1

返回数组中的第一个元素

const head = arr => arr[0];

head([1, 2, 3]);
// => 1

head([]);
// => undefined

思路:直接返回数组的第一个元素。

flatten

将多维数组转化为一维数组

const flatten = arr => [].concat(...arr);

flatten([1, [2, [3, [4]], 5]]);
// => [1, 2, [3, [4]], 5]

思路:使用扩展运算符展开多维数组,再使用 concat 方法将展开后的一维数组拼接起来,得到最终的一维数组。

flattenDeep

将多维数组转化为一维数组,递归进行

const flattenDeep = arr =>
  [].concat(...arr.map(v => (Array.isArray(v) ? flattenDeep(v) : v)));

flattenDeep([1, [2, [3, [4]], 5]]);
// => [1, 2, 3, 4, 5]

思路:使用 Array.map 遍历数组,对于数组中的每个元素,如果是数组则递归调用 flattenDeep 函数,否则直接返回该元素,最终使用扩展运算符展开数组,并使用 concat 方法拼接起来。

fromPairs

将一个二维数组转化为一个对象

const fromPairs = arr =>
  arr.reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {});

fromPairs([
  ['a', 1],
  ['b', 2],
]);
// => { 'a': 1, 'b': 2 }

思路:使用 Array.reduce 遍历数组,对于数组中的每个元素,将其解构为 key 和 val,并将其添加到一个新对象中,最终得到一个包含所有 key-value 对的对象。

indexOf

返回一个元素在数组中的下标,从前往后找

const indexOf = (arr, val, fromIndex = 0) =>
  arr.findIndex((item, index) => index >= fromIndex && item === val);

indexOf([1, 2, 1, 2], 2);
// => 1

// Search from the `fromIndex`.
indexOf([1, 2, 1, 2], 2, 2);
// => 3

思路:使用数组的 findIndex 方法查找,并且支持从指定索引位置开始查找。

initial

返回一个新数组,去掉原数组中的最后一个元素

const initial = arr => arr.slice(0, -1);

initial([1, 2, 3]);
// => [1, 2]

思路:使用数组的 slice 方法截取出除了最后一个元素的部分,得到一个新数组。

intersection

返回一个数组,包含在所有数组中都存在的元素

const intersection = (...arr) => [
  ...new Set(arr.reduce((a, b) => a.filter(v => b.includes(v)))),
];

intersection([2, 1], [4, 2], [1, 2]);
// => [2]

思路:使用数组的 reduce 方法遍历所有数组,使用 filter 方法筛选出在当前数组中也存在的元素,最后使用 Set 去重并转换为数组。

join

将数组转化为字符串,并用指定的分隔符分隔

const join = (arr, separator = ',') =>
  arr.reduce((res, val, i) => `${res}${i ? separator : ''}${val}`, '');

join(['a', 'b', 'c'], '~');
// => 'a~b~c'

思路:使用 reduce 方法遍历数组,将每个元素和分隔符拼接起来,最终得到一个拼接好的字符串。

last

返回数组中的最后一个元素

const last = arr => arr[arr.length - 1];

last([1, 2, 3]);
// => 3

思路:返回数组中的最后一个元素。

lastIndexOf

返回一个元素在数组中的下标,从后往前找

const lastIndexOf = (arr, val) => arr.lastIndexOf(val);

lastIndexOf([1, 2, 1, 2], 2);
// => 3

// Search from the `fromIndex`.
lastIndexOf([1, 2, 1, 2], 2, 2);
// => 1

思路:使用数组的 lastIndexOf 方法查找元素在数组中的下标。

pull

从数组中去掉指定的元素

const pull = (arr, ...args) => arr.filter(item => !args.includes(item));

const array = [1, 2, 3, 1, 2, 3];

pull(array, 2, 3);
console.log(array);
// => [1, 1]

思路:通过 filter 方法筛选掉不需要的元素即可。

pullAt

从数组中取出指定下标的元素,并返回一个新数组

const pullAt = (arr, ...args) => args.map(index => arr.splice(index, 1)[0]);

const array = [5, 10, 15, 20];
const evens = pullAt(array, 1, 3);

console.log(array);
// => [5, 15]

console.log(evens);
// => [10, 20]

思路:map() 方法遍历传入的下标数组,通过 splice() 方法从原数组中删除相应的元素并返回。

reverse

反转数组

const reverse = arr => [...arr].reverse();

const array = [1, 2, 3];

reverse(array);
// => [3, 2, 1]

console.log(array);
// => [3, 2, 1]

思路:利用解构赋值和 reverse() 方法即可。

slice

返回一个新数组,从原数组中截取指定范围的元素

const slice = (arr, start, end) => arr.slice(start, end);

思路:直接调用原生的 slice() 方法。

sortedIndex

返回一个元素应该插入到数组中的下标

const sortedIndex = (arr, value) => {
  let left = 0;
  let right = arr.length;
  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] < value) {
      left = mid + 1;
    } else {
      right = mid;
    }
  }
  return right;
};

sortedIndex([30, 50], 40);
// => 1

思路:二分查找算法实现,找到元素应该插入的位置。

tail

返回一个新数组,去掉原数组中的第一个元素

const tail = arr => arr.slice(1);

tail([1, 2, 3]);
// => [2, 3]

思路:利用 slice() 方法去掉第一个元素即可。

take

返回一个新数组,包含原数组中前 n 个元素

const take = (arr, n = 1) => arr.slice(0, n);

take([1, 2, 3]);
// => [1]

take([1, 2, 3], 2);
// => [1, 2]

take([1, 2, 3], 5);
// => [1, 2, 3]

take([1, 2, 3], 0);
// => []

思路:直接调用原生的 slice() 方法,注意默认参数。

takeRight

返回一个新数组,包含原数组中后 n 个元素

const takeRight = (arr, n = 1) => arr.slice(-n);

takeRight([1, 2, 3]);
// => [3]

takeRight([1, 2, 3], 2);
// => [2, 3]

takeRight([1, 2, 3], 5);
// => [1, 2, 3]

takeRight([1, 2, 3], 0);
// => []

思路:同样直接调用原生的 slice() 方法,注意负数下标的使用。

union

返回一个新数组,包含所有数组中的不重复元素

const union = (...args) => [...new Set(args.flat())];

union([2], [1, 2]);
// => [2, 1]

思路:flat() 方法将多维数组转为一维,Set 数据结构去重,再转回数组即可。

uniq

返回一个新数组,包含所有数组中的不重复元素

const uniq = arr => [...new Set(arr)];

uniq([2, 1, 2]);
// => [2, 1]

思路:同样利用 Set() 数据结构去重。

without

返回一个新数组,去掉原数组中指定的元素

const without = (arr, ...args) => arr.filter(item => !args.includes(item));

without([2, 1, 2, 3], 1, 2);
// => [3]

思路:同 pull 方法。

xor

返回一个新数组,包含只在其中一个数组中出现过的元素

const xor = (...args) =>
  args
    .flat()
    .filter(
      item => args.flat().indexOf(item) === args.flat().lastIndexOf(item)
    );

xor([2, 1], [2, 3]);
// => [1, 3]

思路:flat() 方法转换成一维数组,然后利用 filter() 方法和 indexOf()、lastIndexOf() 方法判断出只在一个数组中出现过的元素。

zip

将多个数组的同一位置的元素合并为一个数组

const zip = (...arrays) =>
  arrays[0].map((_, i) => arrays.map(array => array[i]));

zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]

思路:使用了 Rest 参数,首先取出第一个数组,然后使用 map 遍历第一个数组的长度,通过索引遍历所有数组的该索引元素,将其组合成一个新的数组并返回。

unzip

将 zip 函数生成的数组还原成原始的数组

const unzip = array =>
  array.reduce(
    (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
    Array.from({ length: Math.max(...array.map(a => a.length)) }).map(() => [])
  );

const zipped = zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]

unzip(zipped);
// => [['fred', 'barney'], [30, 40], [true, false]]

思路:使用 reduce 遍历 zip 函数生成的数组,将每个元素的每个值取出来,根据索引组成新的数组返回。在 reduce 函数的初始值中,使用了 Math.max 获取所有元素中最大长度,并通过 Array.from 创建对应长度的二维数组。

dropWhile

返回一个新数组,去掉原数组中从开始到第一个符合条件的元素之间的元素

const dropWhile = (array, predicate) =>
  array.slice(array.findIndex(val => !predicate(val)));

const users = [
  { user: 'barney', active: false },
  { user: 'fred', active: false },
  { user: 'pebbles', active: true },
];

dropWhile(users, o => !o.active);
// => objects for ['pebbles']

// The `matches` iteratee shorthand.
dropWhile(users, { user: 'barney', active: false });
// => objects for ['fred', 'pebbles']

// The `matchesProperty` iteratee shorthand.
dropWhile(users, ['active', false]);
// => objects for ['pebbles']

// The `property` iteratee shorthand.
dropWhile(users, 'active');
// => objects for ['barney', 'fred', 'pebbles']

思路:使用 findIndex 函数找到第一个不符合条件的元素,然后使用 slice 函数截取该元素之后的数组并返回。

intersectionBy

与 intersection 类似,但是可以指定一个函数对比数组中的元素

const intersectionBy = (arr1, arr2, compareFn) =>
  arr1.filter(item =>
    arr2.some(compareItem => compareFn(item) === compareFn(compareItem))
  );

intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
// => [2.1]

// The `property` iteratee shorthand.
intersectionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x');
// => [{ 'x': 1 }]

思路:使用数组的 filter 方法遍历第一个数组,并使用参数指定的函数对比第二个数组的每一个元素,最终返回两个数组的交集。

pullAll

与 pull 类似,但是接收一个数组作为参数

const pullAll = (arr, values) => arr.filter(item => !values.includes(item));

const array = [1, 2, 3, 1, 2, 3];

pullAll(array, [2, 3]);
console.log(array);
// => [1, 1]

思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素。

pullAllBy

与 pullBy 类似,但是接收一个数组作为参数

const pullAllBy = (arr, values, compareFn) =>
  arr.filter(
    item =>
      !values.some(compareItem => compareFn(item) === compareFn(compareItem))
  );

const array = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 1 }];

pullAllBy(array, [{ x: 1 }, { x: 3 }], 'x');
console.log(array);
// => [{ 'x': 2 }]

思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素,使用参数指定的函数对比两个数组中的元素。

pullAllWith

与 pullWith 类似,但是接收一个数组作为参数

const pullAllWith = (arr, values, compareFn) =>
  arr.filter(item => !values.some(compareItem => compareFn(item, compareItem)));

const array = [
  { x: 1, y: 2 },
  { x: 3, y: 4 },
  { x: 5, y: 6 },
];

pullAllWith(array, [{ x: 3, y: 4 }], isEqual);
console.log(array);
// => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]

思路:使用数组的 filter 方法遍历原数组,排除传入的数组中存在的元素,使用参数指定的函数对比两个数组中的元素。

sortedIndexOf

与 indexOf 类似,但是可以在已排序的数组中使用

const sortedIndexOf = (arr, value) => {
  let left = 0;
  let right = arr.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === value) {
      return mid;
    }
    if (arr[mid] < value) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }
  return -1;
};

sortedIndexOf([4, 5, 5, 5, 6], 5);
// => 1

思路:使用二分查找算法在已排序的数组中查找指定元素的位置。

sortedLastIndexOf

与 lastIndexOf 类似,但是可以在已排序的数组中使用

const sortedLastIndexOf = (arr, value) => {
  let left = 0;
  let right = arr.length - 1;
  let lastIndex = -1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === value) {
      lastIndex = mid;
      left = mid + 1;
    } else if (arr[mid] < value) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }
  return lastIndex;
};

sortedLastIndex([4, 5, 5, 5, 6], 5);
// => 4

思路:使用二分查找算法在已排序的数组中查找指定元素的最后一个位置。

sortedUniq

与 uniq 类似,但是可以在已排序的数组中使用

const sortedUniq = arr =>
  arr.reduce((result, item) => {
    if (result.length === 0 || result[result.length - 1] !== item) {
      result.push(item);
    }
    return result;
  }, []);

sortedUniq([1, 1, 2]);
// => [1, 2]

思路:使用数组的 reduce 方法和 indexOf 方法过滤掉重复元素,并返回新数组。

sortedUniqBy

与 uniqBy 类似,但是可以在已排序的数组中使用

const sortedUniqBy = (array, iteratee) =>
  array.reduce(
    (result, value) =>
      result.length && iteratee(value) === iteratee(result[result.length - 1])
        ? result
        : [...result, value],
    []
  );

sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
// => [1.1, 2.3]

思路:使用 reduce 方法,对原数组进行遍历,将每个元素通过指定函数转化为一个值,并使用 Set 对象来去重,最终返回去重后的数组。

takeWhile

返回一个新数组,包含原数组中从开始到第一个不符合条件的元素之间的元素

const takeWhile = (array, predicate) =>
  array.slice(
    0,
    array.findIndex(element => !predicate(element))
  );

思路:使用 findIndex 方法,查找第一个不符合条件的元素的索引,然后使用 slice 方法截取原数组中符合条件的部分,返回新数组。

takeRightWhile

返回一个新数组,包含原数组中从最后一个不符合条件的元素到结尾之间的元素

const takeRightWhile = (array, predicate) =>
  array
    .reverse()
    .slice(
      0,
      array.findIndex(element => !predicate(element))
    )
    .reverse();

const users = [
  { user: 'barney', active: true },
  { user: 'fred', active: false },
  { user: 'pebbles', active: false },
];

takeRightWhile(users, o => !o.active);
// => objects for ['fred', 'pebbles']

// The `matches` iteratee shorthand.
takeRightWhile(users, { user: 'pebbles', active: false });
// => objects for ['pebbles']

// The `matchesProperty` iteratee shorthand.
takeRightWhile(users, ['active', false]);
// => objects for ['fred', 'pebbles']

// The `property` iteratee shorthand.
takeRightWhile(users, 'active');
// => []

思路:使用 reverse 方法反转原数组,然后使用 findIndex 方法查找反转后数组中第一个不符合条件的元素的索引,最终使用 slice 方法截取原数组中从该索引到结尾的部分,返回新数组。

unionBy

与 union 类似,但是可以指定一个函数对比数组中的元素

const unionBy = (...arrays) => {
  const iteratee = arrays.pop();
  const unionSet = new Set(
    arrays.reduce((result, array) => [...result, ...array], []).map(iteratee)
  );
  return Array.from(unionSet);
};

unionBy([2.1], [1.2, 2.3], Math.floor);
// => [2.1, 1.2]

// The `property` iteratee shorthand.
unionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x');
// => [{ 'x': 1 }, { 'x': 2 }]

思路:使用 Set 对象进行数组去重,通过指定函数将多个数组中的元素转化为同一值,最终将去重后的值转化为数组返回。

uniqBy

与 uniq 类似,但是可以指定一个函数对比数组中的元素

const uniqBy = (array, iteratee) => {
  const uniqSet = new Set(array.map(iteratee));
  return Array.from(uniqSet).map(value =>
    array.find(element => iteratee(element) === value)
  );
};

uniqBy([2.1, 1.2, 2.3], Math.floor);
// => [2.1, 1.2]

// The `property` iteratee shorthand.
uniqBy([{ x: 1 }, { x: 2 }, { x: 1 }], 'x');
// => [{ 'x': 1 }, { 'x': 2 }]

思路:同 unionBy 方法实现思路类似。

unzipWith

与 unzip 类似,但是可以指定一个函数来处理 zip 函数生成的数组

const unzipWith = (array, iteratee) =>
  array.reduce(
    (result, value) => (
      value.forEach((innerValue, index) =>
        result[index].push(iteratee ? iteratee(innerValue) : innerValue)
      ),
      result
    ),
    Array.from({ length: Math.max(...array.map(value => value.length)) }).map(
      () => []
    )
  );

const zipped = zip([1, 2], [10, 20], [100, 200]);
// => [[1, 10, 100], [2, 20, 200]]

unzipWith(zipped, add);
// => [3, 30, 300]

思路:使用 reduce 方法,对传入的数组进行遍历,将每个子数组进行传入的函数处理后,将处理后的值存储到对应索引位置上,最终返回处理后的数组。

xorBy

与 xor 类似,但是可以指定一个函数对比数组中的元素

const xorBy = (...arrays) => {
  const iteratee = arrays.pop();
  const valueCount = new Map();
  arrays
    .reduce((result, array) => [...result, ...array], [])
    .forEach((value) => {
      const newValue = iteratee(value);

xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
// => [1.2, 3.4]

// The `property` iteratee shorthand.
xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
// => [{ 'x': 2 }]

思路:使用 reduce 方法,对传入的多个数组进行遍历,将每个数组中的元素转化为指定值,并使用 Map 对象统计每个值出现的次数,最后返回出现次数为 1 的元素组成的数组。

zipObject

将两个数组转化为一个对象

const zipObject = (keys, values) =>
  keys.reduce((obj, key, i) => ({ ...obj, [key]: values[i] }), {});

zipObject(['a', 'b'], [1, 2]);
// => { 'a': 1, 'b': 2 }

思路:使用 reduce 函数遍历 keys 数组,每次将 keys 数组中的一个元素作为属性名,values 数组中相同位置的元素作为属性值,然后将它们存入一个新对象中,最后返回该对象。

zipObjectDeep

将两个数组转化为一个嵌套对象

const zipObjectDeep = (keys, values) =>
  keys.reduce((obj, key, i) => {
    const path = key.split('.');
    const lastKey = path.pop();
    const nestedObj = path.reduceRight(
      (nested, prop) => ({ [prop]: nested }),
      values[i]
    );
    return mergeWith(obj, { [lastKey]: nestedObj }, customizer);
  }, {});

zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }

思路:使用 reduce 函数遍历 keys 数组,每次将 keys 数组中的一个元素拆分成一个路径数组,使用 reduceRight 函数从路径数组的最后一个元素开始遍历,每次将该元素作为属性名,前一个元素对应的对象作为属性值,然后将它们存入一个新对象中,最后返回该对象。