countBy

统计数组中每个元素出现的次数

const countBy = (arr, fn) =>
  arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => {
    acc[val] = (acc[val] || 0) + 1;
    return acc;
  }, {});
​
countBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': 1, '6': 2 }
​
// The `property` iteratee shorthand.
countBy(['one', 'two', 'three'], 'length');
// => { '3': 2, '5': 1 }

思路:使用 map 将数组每个元素进行映射,fn 是函数时直接执行,否则取对应属性,再使用 reduce 将元素出现的次数进行累加

each

遍历数组或对象,并对每个元素执行指定的函数

// each -> forEach
const each = (collection, fn) => {
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      fn(collection[i], i, collection);
    }
  } else {
    for (const key in collection) {
      if (collection.hasOwnProperty(key)) {
        fn(collection[key], key, collection);
      }
    }
  }
};
​
forEach([1, 2], value => {
  console.log(value);
});
// => Logs `1` then `2`.
​
forEach({ a: 1, b: 2 }, (value, key) => {
  console.log(key);
});
// => Logs 'a' then 'b' (iteration order is not guaranteed).

思路:如果是数组,则使用 for 循环遍历,对每个元素执行 fn 函数;如果是对象,则使用 for-in 循环遍历,对每个属性执行 fn 函数

filter

遍历数组或对象,返回符合条件的元素

const filter = (collection, predicate) =>
  collection.filter(
    typeof predicate === 'function' ? predicate : val => val[predicate]
  );
​
const users = [
  { user: 'barney', age: 36, active: true },
  { user: 'fred', age: 40, active: false },
];
​
filter(users, o => !o.active);
// => objects for ['fred']
​
// The `matches` iteratee shorthand.
filter(users, { age: 36, active: true });
// => objects for ['barney']
​
// The `matchesProperty` iteratee shorthand.
filter(users, ['active', false]);
// => objects for ['fred']
​
// The `property` iteratee shorthand.
filter(users, 'active');
// => objects for ['barney']

思路:使用 filter 方法过滤符合条件的元素,predicate 是函数时直接执行,否则判断元素是否具有对应的属性值

find

遍历数组或对象,返回第一个符合条件的元素

const find = (collection, predicate) =>
  collection.filter(
    typeof predicate === 'function' ? predicate : val => val[predicate]
  )[0];
​
const users = [
  { user: 'barney', age: 36, active: true },
  { user: 'fred', age: 40, active: false },
  { user: 'pebbles', age: 1, active: true },
];
​
find(users, o => o.age < 40);
// => object for 'barney'
​
// The `matches` iteratee shorthand.
find(users, { age: 1, active: true });
// => object for 'pebbles'
​
// The `matchesProperty` iteratee shorthand.
find(users, ['active', false]);
// => object for 'fred'
​
// The `property` iteratee shorthand.
find(users, 'active');
// => object for 'barney'

思路:使用 filter 方法过滤符合条件的元素,并返回第一个符合条件的元素,predicate 是函数时直接执行,否则判断元素是否具有对应的属性值

flatMap

遍历数组,将每个元素映射成一个新的数组,再将多个数组合并成一个新数组

const flatMap = (arr, fn) => arr.reduce((acc, val) => acc.concat(fn(val)), []);
​
function duplicate(n) {
  return [n, n];
}
​
flatMap([1, 2], duplicate);
// => [1, 1, 2, 2]

思路:使用 reduce 方法遍历数组,将每个元素映射成一个新的数组,然后使用 concat 将多个数组合并成一个新数组。

flatMapDeep

遍历数组,将每个元素映射成一个新的数组,递归进行,再将多个数组合并成一个新数组

const flatMapDeep = (arr, fn) =>
  arr.reduce(
    (acc, val) =>
      acc.concat(Array.isArray(val) ? flatMapDeep(val, fn) : fn(val)),
    []
  );
​
function duplicate(n) {
  return [[[n, n]]];
}
​
flatMapDeep([1, 2], duplicate);
// => [1, 1, 2, 2]

思路:使用 reduce 遍历数组,将每个元素映射成一个新的数组,如果是数组则递归进行,最后使用 concat 将多个数组合并成一个新数组

flatMapDepth

遍历数组,将每个元素映射成一个新的数组,指定递归的深度,再将多个数组合并成一个新数组

const flatMapDepth = (array, iteratee, depth = 1) =>
  depth > 0
    ? array.reduce((acc, cur) => {
        const mapped = iteratee(cur);
        return acc.concat(
          Array.isArray(mapped)
            ? flatMapDepth(mapped, iteratee, depth - 1)
            : mapped
        );
      }, [])
    : array.slice();
​
function duplicate(n) {
  return [[[n, n]]];
}
​
flatMapDepth([1, 2], duplicate, 2);
// => [[1, 1], [2, 2]]

思路:使用递归的方式实现深度优先遍历数组,然后将每个元素映射成一个新的数组,最后将多个数组合并成一个新数组。

forEach

遍历数组或对象,并对每个元素执行指定的函数,与 each 函数类似

const forEach = (collection, iteratee) => {
  for (const value of collection) {
    iteratee(value);
  }
};
​
_([1, 2]).forEach(value => {
  console.log(value);
});
// => Logs `1` then `2`.
​
forEach({ a: 1, b: 2 }, (value, key) => {
  console.log(key);
});
// => Logs 'a' then 'b' (iteration order is not guaranteed).

思路:使用 for...of 循环遍历数组或对象,并对每个元素执行指定的函数。

groupBy:按照指定的方式对数组进行分组

const groupBy = (array, iteratee) =>
  array.reduce((acc, cur) => {
    const key = typeof iteratee === 'function' ? iteratee(cur) : cur[iteratee];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(cur);
    return acc;
  }, {});
​
groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }
​
// The `property` iteratee shorthand.
groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }

思路:使用 reduce 方法遍历数组,按照指定的方式对数组进行分组。

includes

判断一个元素是否在数组或对象中

const includes = (collection, value) => collection.includes(value);
​
includes([1, 2, 3], 1);
// => true
​
includes([1, 2, 3], 1, 2);
// => false
​
includes({ user: 'fred', age: 40 }, 'fred');
// => true
​
includes('pebbles', 'eb');
// => true

思路:使用 Array.includes 方法判断一个元素是否在数组或对象中。

invokeMap

对数组中的每个元素调用指定的方法,并返回结果

const invokeMap = (array, path, ...args) =>
  array.map(value => {
    const method = typeof path === 'function' ? path : value[path];
    return method.apply(value, args);
  });
​
invokeMap(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  'sort'
);
// => [[1, 5, 7], [1, 2, 3]]
​
invokeMap([123, 456], String.prototype.split, '');
// => [['1', '2', '3'], ['4', '5', '6']]

思路:使用 Array.map 方法对数组中的每个元素调用指定的方法,并返回结果。

keyBy

将数组转化为对象,对象的键值是指定属性的值,值是该元素

const keyBy = (arr, key) =>
  arr.reduce((acc, val) => ((acc[val[key]] = val), acc), {});
​
const array = [
  { dir: 'left', code: 97 },
  { dir: 'right', code: 100 },
];
​
keyBy(array, o => String.fromCharCode(o.code));
// => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
​
keyBy(array, 'dir');
// => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }

思路:使用 reduce 方法遍历数组,将数组转化为对象,对象的键值是指定属性的值,值是该元素。

map:遍历数组或对象,将每个元素映射成一个新的元素

const map = (arr, func) => arr.reduce((acc, val) => [...acc, func(val)], []);
​
function square(n) {
  return n * n;
}
​
map([4, 8], square);
// => [16, 64]
​
map({ a: 4, b: 8 }, square);
// => [16, 64] (iteration order is not guaranteed)
​
const users = [{ user: 'barney' }, { user: 'fred' }];
​
// The `property` iteratee shorthand.
map(users, 'user');
// => ['barney', 'fred']

思路:使用 reduce 方法遍历数组,将每个元素映射成一个新的元素。

orderBy

按照指定的方式对数组进行排序

const orderBy = (arr, props, orders) =>
  [...arr].sort((a, b) =>
    props.reduce((acc, prop, i) => {
      if (acc === 0) {
        const [p1, p2] =
          orders && orders[i] === 'desc'
            ? [b[prop], a[prop]]
            : [a[prop], b[prop]];
        acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
      }
      return acc;
    }, 0)
  );
​
const users = [
  { user: 'fred', age: 48 },
  { user: 'barney', age: 34 },
  { user: 'fred', age: 40 },
  { user: 'barney', age: 36 },
];
​
// 以 `user` 升序排序 再  `age` 以降序排序。
orderBy(users, ['user', 'age'], ['asc', 'desc']);
// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

思路:使用 sort 方法对数组进行排序,props 参数表示排序的属性,orders 参数表示排序的顺序。

partition

按照指定的条件对数组进行分割

const partition = (arr, func) =>
  arr.reduce((acc, val) => (acc[func(val) ? 0 : 1].push(val), acc), [[], []]);
​
const users = [
  { user: 'barney', age: 36, active: false },
  { user: 'fred', age: 40, active: true },
  { user: 'pebbles', age: 1, active: false },
];
​
partition(users, o => o.active);
// => objects for [['fred'], ['barney', 'pebbles']]
​
// The `matches` iteratee shorthand.
partition(users, { age: 1, active: false });
// => objects for [['pebbles'], ['barney', 'fred']]
​
// The `matchesProperty` iteratee shorthand.
partition(users, ['active', false]);
// => objects for [['barney', 'pebbles'], ['fred']]
​
// The `property` iteratee shorthand.
partition(users, 'active');
// => objects for [['fred'], ['barney', 'pebbles']]

思路:使用 reduce 方法遍历数组,按照指定的条件对数组进行分割。

reduce

遍历数组或对象,累加每个元素到累加器中

const reduce = (arr, func, initVal) => {
  let acc = initVal;
  for (let i = 0; i < arr.length; i++) {
    acc = func(acc, arr[i], i, arr);
  }
  return acc;
};
​
reduce([1, 2], (sum, n) => sum + n, 0);
// => 3
​
reduce(
  { a: 1, b: 2, c: 1 },
  (result, value, key) => {
    (result[value] || (result[value] = [])).push(key);
    return result;
  },
  {}
);
// => { '1': ['a', 'c'], '2': ['b'] } (无法保证遍历的顺序)

思路:使用 for 循环遍历数组,累加每个元素到累加器中。

reduceRight

与 reduce 类似,但是从数组的末尾开始遍历

const reduceRight = (arr, func, initVal) => {
  let acc = initVal;
  for (let i = arr.length - 1; i >= 0; i--) {
    acc = func(acc, arr[i], i, arr);
  }
  return acc;
};
​
const array = [
  [0, 1],
  [2, 3],
  [4, 5],
];
​
reduceRight(array, (flattened, other) => flattened.concat(other), []);
// => [4, 5, 2, 3, 0, 1]

思路:与 reduce 类似,但是从数组的末尾开始遍历。

reject

遍历数组或对象,返回不符合条件的元素

const reject = (arr, fn) => arr.filter(x => !fn(x));
​
const users = [
  { user: 'barney', age: 36, active: false },
  { user: 'fred', age: 40, active: true },
];
​
reject(users, o => !o.active);
// => objects for ['fred']
​
// `matches` 迭代简写
reject(users, { age: 40, active: true });
// => objects for ['barney']
​
// `matchesProperty` 迭代简写
reject(users, ['active', false]);
// => objects for ['fred']
​
// `property` 迭代简写
reject(users, 'active');
// => objects for ['barney']

思路:使用 filter 方法遍历数组,返回不符合条件的元素即可。

sample

随机返回数组或对象中的一个元素

const sample = arr => arr[Math.floor(Math.random() * arr.length)];
​
sample([1, 2, 3, 4]);
// => 2

思路:使用 Math.random 方法生成随机数,再根据数组的长度随机获取一个元素。

sampleSize

随机返回数组或对象中的多个元素

const sampleSize = (arr, n = 1) =>
  arr.sort(() => Math.random() - 0.5).slice(0, n);
​
sampleSize([1, 2, 3], 2);
// => [3, 1]
​
sampleSize([1, 2, 3], 4);
// => [2, 3, 1]

思路:使用 sort 方法和 Math.random 方法打乱数组顺序,再使用 slice 方法截取指定数量的元素。

shuffle

随机打乱数组或对象中的元素

const shuffle = arr => arr.sort(() => Math.random() - 0.5);
​
shuffle([1, 2, 3, 4]);
// => [4, 1, 3, 2]

思路:使用 sort 方法和 Math.random 方法打乱数组顺序即可。

size

返回数组或对象的长度或元素个数

const size = obj => Object.keys(obj).length;
​
size([1, 2, 3]);
// => 3
​
size({ a: 1, b: 2 });
// => 2
​
size('pebbles');
// => 7

思路:使用 Object.keys 方法获取对象属性的数组,再使用 length 属性获取长度即可。

some

遍历数组或对象,判断是否至少有一个元素符合条件

const some = (arr, fn) => arr.some(fn);
​
some([null, 0, 'yes', false], Boolean);
// => true
​
const users = [
  { user: 'barney', active: true },
  { user: 'fred', active: false },
];
​
// The `matches` iteratee shorthand.
some(users, { user: 'barney', active: false });
// => false
​
// The `matchesProperty` iteratee shorthand.
some(users, ['active', false]);
// => true
​
// The `property` iteratee shorthand.
some(users, 'active');
// => true

思路:使用 some 方法判断是否至少有一个元素符合条件。

sortBy

按照指定的方式对数组进行排序

const sortBy = (arr, fn) => arr.sort((a, b) => fn(a) - fn(b));
​
const users = [
  { user: 'fred', age: 48 },
  { user: 'barney', age: 36 },
  { user: 'fred', age: 40 },
  { user: 'barney', age: 34 },
];
​
sortBy(users, o => o.user);
// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
​
sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
​
sortBy(users, 'user', o => Math.floor(o.age / 10));
// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

思路:使用 sort 方法和传入的函数进行排序即可。