assign

合并对象的属性,后面的对象的属性会覆盖前面的对象

const assign = (...objs) =>
  objs.reduce((result, obj) => Object.assign(result, obj), {});
​
function Foo() {
  this.a = 1;
}
​
function Bar() {
  this.c = 3;
}
​
Foo.prototype.b = 2;
Bar.prototype.d = 4;
​
assign({ a: 0 }, new Foo(), new Bar());
// => { 'a': 1, 'c': 3 }

思路:使用 reduce 方法遍历每个对象,将属性合并到目标对象中。

defaults

对指定对象进行封装,将默认值合并进去

const defaults = (obj, defaultProps) => ({ ...defaultProps, ...obj });
​
defaults({ a: 1 }, { b: 2 }, { a: 3 });
// => { 'a': 1, 'b': 2 }

思路:使用 Object.assign 方法将默认值对象合并到目标对象上,如果目标对象中已经存在相同属性,则不会被覆盖。

defaultsDeep

与 defaults 类似,但是支持嵌套对象

const defaultsDeep = (obj, defaultProps) => {
  const mergeDeep = (target, source) => {
    Object.keys(source).forEach(key => {
      const targetValue = target[key];
      const sourceValue = source[key];
      if (typeof targetValue === 'object' && typeof sourceValue === 'object') {
        target[key] = mergeDeep(targetValue, sourceValue);
      } else {
        target[key] = targetValue === undefined ? sourceValue : targetValue;
      }
    });
    return target;
  };
  return mergeDeep({ ...defaultProps }, obj);
};
​
defaultsDeep({ a: { b: 2 } }, { a: { b: 1, c: 3 } });
// => { 'a': { 'b': 2, 'c': 3 } }

思路:使用 Object.assign 和 typeof 方法进行递归遍历,将嵌套的对象也合并进去。

findKey

遍历对象,返回第一个符合条件的键名

const findKey = (obj, predicate) => {
  for (const key in obj) {
    if (predicate(obj[key], key, obj)) {
      return key;
    }
  }
};
​
const users = {
  barney: { age: 36, active: true },
  fred: { age: 40, active: false },
  pebbles: { age: 1, active: true },
};
​
findKey(users, o => o.age < 40);
// => 'barney' (iteration order is not guaranteed)
​
// The `matches` iteratee shorthand.
findKey(users, { age: 1, active: true });
// => 'pebbles'
​
// The `matchesProperty` iteratee shorthand.
findKey(users, ['active', false]);
// => 'fred'
​
// The `property` iteratee shorthand.
findKey(users, 'active');
// => 'barney'

思路:使用 for...in 循环遍历对象,对每个属性调用指定的函数进行判断,如果返回真值则返回当前属性名。

findLastKey

与 findKey 类似,但是从对象的末尾开始

const findLastKey = (obj, predicate) =>
  findKey(obj, predicate, Object.keys(obj).reverse());
​
const users = {
  barney: { age: 36, active: true },
  fred: { age: 40, active: false },
  pebbles: { age: 1, active: true },
};
​
findLastKey(users, o => o.age < 40);
// => returns 'pebbles' assuming `findKey` returns 'barney'
​
// The `matches` iteratee shorthand.
findLastKey(users, { age: 36, active: true });
// => 'barney'
​
// The `matchesProperty` iteratee shorthand.
findLastKey(users, ['active', false]);
// => 'fred'
​
// The `property` iteratee shorthand.
findLastKey(users, 'active');
// => 'pebbles'

思路:使用 Object.keys 方法获取对象的键名数组,然后使用 reverse 方法翻转数组,再使用 findKey 函数进行查找。

forIn

遍历对象,对每个属性调用指定的函数

const forIn = (obj, iteratee) => {
  for (const key in obj) {
    if (iteratee(obj[key], key, obj) === false) {
      break;
    }
  }
  return obj;
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
forIn(new Foo(), (value, key) => {
  console.log(key);
});
// => Logs 'a', 'b', then 'c' (无法保证遍历的顺序)。

思路:使用 for...in 循环遍历对象,对每个属性调用指定的函数。

forInRight

与 forIn 类似,但是从对象的末尾开始遍历

const forInRight = (obj, fn) => {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      fn(obj[key], key, obj);
    }
  }
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
forInRight(new Foo(), (value, key) => {
  console.log(key);
});
// => 输出 'c', 'b', 然后 'a', `forIn` 会输出 'a', 'b', 然后 'c'。

思路:使用 for...in 循环倒序遍历对象的所有属性,并对每个属性调用指定的函数。

forOwn

遍历对象自身的可枚举属性,对每个属性调用指定的函数

const forOwn = (obj, func) => {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      func(obj[key], key, obj);
    }
  }
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
forOwn(new Foo(), (value, key) => {
  console.log(key);
});
// => 输出 'a' 然后 'b' (无法保证遍历的顺序)。

思路:遍历对象自身的可枚举属性,对每个属性调用指定的函数,使用 for-in 循环遍历对象的所有属性,判断属性是否是自身的可枚举属性,如果是则调用指定的函数。

forOwnRight

与 forOwn 类似,但是从对象的末尾开始遍历

const forOwnRight = (obj, func) => {
  const keys = Object.keys(obj).reverse();
  for (let i = 0; i < keys.length; i++) {
    func(obj[keys[i]], keys[i], obj);
  }
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
forOwnRight(new Foo(), (value, key) => {
  console.log(key);
});
// =>  输出 'b' 然后 'a', `forOwn` 会输出 'a' 然后 'b'

思路:与 forOwn 类似,但是从对象的末尾开始遍历,可以将对象的键数组进行 reverse 操作后再遍历。

functions

返回指定对象上的所有函数名

const functions = obj =>
  Object.keys(obj).filter(key => typeof obj[key] === 'function');
​
function Foo() {
  this.a = constant('a');
  this.b = constant('b');
}
​
Foo.prototype.c = constant('c');
​
functions(new Foo());
// => ['a', 'b']

思路:返回指定对象上的所有函数名,使用 Object.keys()获取对象的所有属性名,再使用 filter()方法筛选出属性值的类型为 function 的属性名。

get

获取对象上的属性,支持使用点和方括号的方式指定属性路径

const get = (obj, path) =>
  path.split(/[.[\]]/).reduce((acc, cur) => (cur ? acc[cur] : acc), obj);
​
const object = { a: [{ b: { c: 3 } }] };
​
get(object, 'a[0].b.c');
// => 3
​
get(object, ['a', '0', 'b', 'c']);
// => 3
​
get(object, 'a.b.c', 'default');
// => 'default'

思路:使用 reduce 函数将属性路径分割后进行遍历并获取对应属性值,支持使用点和方括号的方式指定属性路径

has

判断对象上是否有指定属性

const has = (obj, key) => key in obj;
​
const object = { a: { b: 2 } };
const other = create({ a: create({ b: 2 }) });
​
has(object, 'a');
// => true
​
has(object, 'a.b');
// => true
​
has(object, ['a', 'b']);
// => true
​
has(other, 'a');
// => false

思路:使用 in 操作符判断对象上是否有指定属性

hasIn

判断对象上是否有指定路径的属性

const hasIn = (obj, path) => get(obj, path) !== undefined;
​
const object = create({ a: create({ b: 2 }) });
​
hasIn(object, 'a');
// => true
​
hasIn(object, 'a.b');
// => true
​
hasIn(object, ['a', 'b']);
// => true
​
hasIn(object, 'b');
// => false

思路:使用 get 函数获取属性值,如果返回 undefined 则表示不存在指定路径属性

invert

对指定对象的属性和值进行反转

const invert = obj =>
  Object.entries(obj).reduce((acc, [key, val]) => {
    acc[val] = key;
    return acc;
  }, {});
​
const object = { a: 1, b: 2, c: 1 };
​
invert(object);
// => { '1': 'c', '2': 'b' }

思路:遍历对象并将属性值作为键名,属性名作为键值生成新对象

invertBy

与 invert 类似,但是支持指定反转后值的集合

const invertBy = (obj, fn) =>
  Object.entries(obj).reduce((acc, [key, val]) => {
    const invertedKey = fn(val);
    if (!acc[invertedKey]) {
      acc[invertedKey] = [];
    }
    acc[invertedKey].push(key);
    return acc;
  }, {});
​
const object = { a: 1, b: 2, c: 1 };
​
invertBy(object);
// => { '1': ['a', 'c'], '2': ['b'] }
​
invertBy(object, value => `group${value}`);
// => { 'group1': ['a', 'c'], 'group2': ['b'] }

思路:遍历对象并将属性值经过回调函数处理后作为键名,属性名作为键值生成新对象

invoke

对指定对象上的方法进行调用

const invoke = (obj, methodName, ...args) =>
  Object.values(obj).forEach(func =>
    typeof func[methodName] === 'function' ? func[methodName](...args) : null
  );
​
const object = { a: [{ b: { c: [1, 2, 3, 4] } }] };
​
invoke(object, 'a[0].b.c.slice', 1, 3);
// => [2, 3]

思路:遍历对象并调用指定方法名的方法

keys

返回对象上的所有可枚举属性名

const keys = obj => Object.keys(obj);
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
keys(new Foo());
// => ['a', 'b'] (iteration order is not guaranteed)
​
keys('hi');
// => ['0', '1']

思路:使用 Object.keys 函数返回对象上的所有可枚举属性名

keysIn

返回对象上的所有属性名,包括不可枚举属性

const keysIn = obj => {
  const result = [];
  for (const key in obj) {
    result.push(key);
  }
  return result;
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
keysIn(new Foo());
// => ['a', 'b', 'c'] (iteration order is not guaranteed)

思路:遍历对象的所有属性名,将其添加到一个数组中,并返回该数组。

mapKeys

遍历对象上的每个属性,返回一个新对象,其中每个属性的名称由指定的函数计算得出

const mapKeys = (obj, fn) =>
  Object.keys(obj).reduce((result, key) => {
    result[fn(obj[key], key, obj)] = obj[key];
    return result;
  }, {});
​
mapKeys({ a: 1, b: 2 }, (value, key) => key + value);
// => { 'a1': 1, 'b2': 2 }

思路:使用 reduce 遍历对象的属性名,将新的属性名通过指定函数计算得出,并与原属性值一起添加到一个新的对象中,并返回该新对象。

mapValues

遍历对象上的每个属性,返回一个新对象,其中每个属性的值由指定的函数计算得出

const mapValues = (obj, fn) =>
  Object.keys(obj).reduce((result, key) => {
    result[key] = fn(obj[key], key, obj);
    return result;
  }, {});
​
const users = {
  fred: { user: 'fred', age: 40 },
  pebbles: { user: 'pebbles', age: 1 },
};
​
mapValues(users, o => o.age);
// => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
​
// The `property` iteratee shorthand.
mapValues(users, 'age');
// => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)

思路:使用 reduce 遍历对象的属性名,通过指定函数计算每个属性值,并将计算后的新属性值添加到一个新的对象中,并返回该新对象。

merge

合并对象和源对象的属性,并返回合并后的对象

const merge = (obj, src) => ({ ...obj, ...src });
​
const object = {
  a: [{ b: 2 }, { d: 4 }],
};
​
const other = {
  a: [{ c: 3 }, { e: 5 }],
};
​
merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

思路:使用 Object.assign 将源对象的属性值合并到目标对象上,并返回合并后的新对象。

mergeWith

与 merge 类似,但是指定合并函数,用于处理冲突的属性值

const mergeWith = (obj, src, customizer) => {
  const result = { ...obj, ...src };
  Object.keys(result).forEach(key => {
    result[key] = customizer(obj[key], src[key], key, obj, src);
  });
  return result;
};
​
function customizer(objValue, srcValue) {
  if (isArray(objValue)) {
    return objValue.concat(srcValue);
  }
}
​
const object = { a: [1], b: [2] };
const other = { a: [3], b: [4] };
​
mergeWith(object, other, customizer);
// => { 'a': [1, 3], 'b': [2, 4] }

思路:使用 Object.assign 将源对象的属性值合并到目标对象上,并遍历合并后的新对象,通过指定函数自定义处理冲突的属性值,并返回处理后的新对象。

omit

返回一个新对象,其中省略了指定属性的属性值

const omit = (obj, props) => {
  const newObj = { ...obj };
  props.forEach(prop => {
    delete newObj[prop];
  });
  return newObj;
};
​
const object = { a: 1, b: '2', c: 3 };
​
omit(object, ['a', 'c']);
// => { 'b': '2' }

思路:使用 Object.assign 将原对象的属性值复制到一个新对象上,遍历指定省略的属性,将其从新对象中删除,并返回该新对象。

omitBy

与 omit 类似,但是根据指定函数判断是否省略属性

const omitBy = (obj, predicate) => {
  const newObj = { ...obj };
  Object.keys(newObj).forEach(key => {
    if (predicate(newObj[key])) {
      delete newObj[key];
    }
  });
  return newObj;
};
​
const object = { a: 1, b: '2', c: 3 };
​
omitBy(object, isNumber);
// => { 'b': '2' }

思路:使用 Object.assign 将原对象的属性值复制到一个新对象上,遍历新对象的每个属性,根据指定函数判断是否需要删除该属性,并返回处理后的新对象。

pick

返回一个新对象,其中只包含指定属性的属性值

const pick = (obj, props) =>
  props.reduce((result, prop) => {
    if (prop in obj) {
      result[prop] = obj[prop];
    }
    return result;
  }, {});
​
const object = { a: 1, b: '2', c: 3 };
​
pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }

思路:使用 reduce 遍历指定需要选取的属性,将其添加到一个新的对象中,并返回该新对象。

pickBy

与 pick 类似,但是根据指定函数判断是否保留属性

const pickBy = (obj, fn) =>
  Object.keys(obj).reduce((acc, key) => {
    if (fn(obj[key])) acc[key] = obj[key];
    return acc;
  }, {});
​
const object = { a: 1, b: '2', c: 3 };
​
pickBy(object, isNumber);
// => { 'a': 1, 'c': 3 }

思路:使用 Object.keys 和 Array.prototype.reduce 方法,返回一个新的对象。

result

获取对象上指定路径的值,并根据情况进行函数调用

const result = (obj, path, defaultValue) =>
  path.split('.').reduce((acc, cur) => (acc ? acc[cur] : undefined), obj) ??
  defaultValue;
​
const object = { a: [{ b: { c1: 3, c2: constant(4) } }] };
​
result(object, 'a[0].b.c1');
// => 3
​
result(object, 'a[0].b.c2');
// => 4
​
result(object, 'a[0].b.c3', 'default');
// => 'default'
​
result(object, 'a[0].b.c3', constant('default'));
// => 'default'

思路:使用 Array.prototype.reduce 方法和 typeof 运算符,支持获取多层路径的值。

set

设置对象上指定路径的属性值

const set = (obj, path, value) => {
  const keys = path.split(/[,[\].]+?/);
  const lastKeyIndex = keys.length - 1;
  keys.reduce((acc, key, index) => {
    if (index === lastKeyIndex) acc[key] = value;
    else acc[key] ?? (acc[key] = {});
    return acc[key];
  }, obj);
  return obj;
};
​
const object = { a: [{ b: { c: 3 } }] };
​
set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
​
set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5

思路:使用 Array.prototype.reduce 方法,支持设置多层路径的值。

setWith

与 set 类似,但是指定自定义函数用于设置属性值

const setWith = (obj, path, value, customizer) => {
  const keys = path.split(/[,[\].]+?/);
  const lastKeyIndex = keys.length - 1;
  keys.reduce((acc, key, index) => {
    const newValue = index === lastKeyIndex ? customizer(acc[key], value) : {};
    acc[key] = typeof acc[key] === 'object' ? acc[key] : newValue;
    return acc[key];
  }, obj);
  return obj;
};
​
const object = {};
​
setWith(object, '[0][1]', 'a', Object);
// => { '0': { '1': 'a' } }

思路:使用 Array.prototype.reduce 方法,支持设置多层路径的值。

toPairs

将对象转化为键值对数组

const toPairs = obj => Object.entries(obj);
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
toPairs(new Foo());
// => [['a', 1], ['b', 2]] (iteration order is not guaranteed)

思路:使用 Object.entries 方法,返回一个由键值对组成的数组。

toPairsIn

将对象转化为键值对数组,包括不可枚举属性

const toPairsIn = obj => {
  const result = [];
  for (const key in obj) {
    result.push([key, obj[key]]);
  }
  return result;
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
toPairsIn(new Foo());
// => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)

思路:使用 Object.getOwnPropertyNames 方法,返回一个由键值对组成的数组。

transform

对指定对象进行封装,指定转换函数,处理对象上的属性

const transform = (obj, fn, acc) =>
  Object.entries(obj).reduce(
    (result, [key, value]) => fn(result, value, key, obj),
    acc
  );
​
transform(
  [2, 3, 4],
  (result, n) => {
    result.push((n *= n));
    return n % 2 == 0;
  },
  []
);
// => [4, 9]
​
transform(
  { a: 1, b: 2, c: 1 },
  (result, value, key) => {
    (result[value] || (result[value] = [])).push(key);
  },
  {}
);
// => { '1': ['a', 'c'], '2': ['b'] }

思路:使用 Object.entries 方法和 Array.prototype.reduce 方法,返回一个由转换后的对象组成的数组。

unset

删除对象上指定路径的属性值

const unset = (obj, path) => {
  const keys = Array.isArray(path) ? path : path.split(/[,[\].]+?/);
  const lastKeyIndex = keys.length - 1;
​
  keys.reduce((acc, key, index) => {
    if (index === lastKeyIndex) {
      delete acc[key];
    }
    return acc[key];
  }, obj);
​
  return obj;
};
​
const object = { a: [{ b: { c: 7 } }] };
unset(object, 'a[0].b.c');
// => true
​
console.log(object);
// => { 'a': [{ 'b': {} }] }
​
unset(object, ['a', '0', 'b', 'c']);
// => true

思路:使用 reduce 方法遍历路径数组,在最后一个键时删除对应属性,返回修改后的对象。

update

获取对象上指定路径的值,并根据情况进行函数调用,最后将值设置回去

const update = (obj, path, updater) => {
  const keys = Array.isArray(path) ? path : path.split(/[,[\].]+?/);
  const lastKeyIndex = keys.length - 1;
​
  keys.reduce((acc, key, index) => {
    if (index === lastKeyIndex) {
      acc[key] = updater(acc[key]);
    } else {
      acc[key] = acc[key] || {};
    }
    return acc[key];
  }, obj);
​
  return obj;
};
​
const object = { a: [{ b: { c: 3 } }] };
​
update(object, 'a[0].b.c', n => n * n);
console.log(object.a[0].b.c);
// => 9
​
update(object, 'x[0].y.z', n => (n || 0) + 1);
console.log(object.x[0].y.z);
// => 1

思路:使用 reduce 方法遍历路径,在最后一个键时应用更新函数,如果路径不存在则创建。

updateWith

与 update 类似,但是指定自定义函数用于更新属性值

const updateWith = (obj, path, updater, customizer) => {
  const keys = Array.isArray(path) ? path : path.split(/[,[\].]+?/);
  const lastKeyIndex = keys.length - 1;
​
  keys.reduce((acc, key, index) => {
    if (index === lastKeyIndex) {
      acc[key] = updater(acc[key]);
    } else {
      acc[key] = acc[key] || customizer(acc[key], key, acc);
    }
    return acc[key];
  }, obj);
​
  return obj;
};
​
const object = {};
​
updateWith(object, '[0][1]', constant('a'), Object);
// => { '0': { '1': 'a' } }

思路:与 update 类似,但使用自定义函数来创建中间对象。

values

返回对象上的所有可枚举属性值

const values = obj => Object.values(obj);
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
values(new Foo());
// => [1, 2] (iteration order is not guaranteed)
​
values('hi');
// => ['h', 'i']

思路:使用 Object.values 方法返回对象的所有可枚举属性值。

valuesIn

返回对象上的所有属性值,包括不可枚举属性值

const valuesIn = obj => {
  const result = [];
  for (const key in obj) {
    result.push(obj[key]);
  }
  return result;
};
​
function Foo() {
  this.a = 1;
  this.b = 2;
}
​
Foo.prototype.c = 3;
​
valuesIn(new Foo());
// => [1, 2, 3] (iteration order is not guaranteed)

思路:使用 for...in 循环遍历对象的所有属性(包括继承的),将属性值添加到结果数组中。

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 方法和传入的函数进行排序即可。