0. 导语

Lodash 是 JavaScript 社区最闻名的一个东西库,供应了许多高效、高兼容性的东西函数。

不过,跟着浏览器和 web 技术的开展,一些人初步反对在项目中运用 lodash。主要原因有:

  • ES6 之后拓展了 JavaScript 特性,许多功用不再需求额定东西库。
  • 现代浏览器对 ES 语法的支撑性提高。
  • Babel 等编译东西能将 ES6 编译成 ES5,更高雅地解决了 ES6 语法的兼容性问题。
  • 为了几个东西函数而引入了整个 lodash,增加了项目的体积。

本文来讨论一下,我们该不该在项目中运用 lodash,以及怎样正确运用 lodash。是的,这个看起来有点标题党的标题,有两层含义:

  • 你会在项目中运用 lodash 吗?
  • 你会正确地运用 lodash 吗?

1. 我们还需求 lodash 吗?

个人认为,应该优先运用 ES 原生语法,一起,在大部分项目中仍然举荐运用 lodash 作为拓展东西库。原因如下:

  • Lodash 不止 ES6,有更多 ES6 难以完结的功用,比如常见的深复制。
  • 进步开发功率、简化代码。lodash 中的函数,都是社区开发者从多年的实践中提炼出来的常用功用,而且通过广泛的检测和优化,运用库函数往往比自己完结有更好的功用和鲁棒性。
  • Lodash 支撑多种模块化方案,合作 tree shaking 技术或许运用单独的函数模块,简直不会导致冗余代码。

不管怎样样,lodash 目前仍然坚持着 4 千多万的周下载了,就足以见得它的流行程度。

2. 仍是不想用 lodash ?

即便你坚持不肯运用 lodash,我认为仍然有必要了解 lodash 供应了哪些功用,这些功用你会常常在开发中遇到。这个时分,你可以从 You Don’t Need Lodash Underscore 中查看怎样运用原生语法完结。

3. Lodash 按需引入

最常见的引入 lodash 的方法是:

// 方法1:引入整个lodash方针
import _ from "lodash";
// 方法2:按名称引入特定的函数
import { cloneDeep } from "lodash";

这两种方法都会引入整个 lodash 库。Lodash 含有许多函数,项目里一般只会用到其间的小部分,为了避免引入不必要的代码,lodash 供应了多种支撑按需加载的方法。

A)运用打包插件完结按需加载(举荐)

插件 babel-plugin-lodash 和 lodash-webpack-plugin 可以在打包时去掉不必要的 lodash 代码,减小产品体积。

B)指用具体的功用模块

// 只引入 array 模块的功用
import array from "lodash/array";
// 只引入 cloneDeep 函数
import cloneDeep from "lodash/cloneDeep";

这种方法只会引入引证途径对应的模块,无需运用插件,也不会有冗余代码。缺陷是每个 import 句子只能引入一个函数,可能导致多个 import 句子。

C)运用单独的函数库

Lodash 为每个方法供应了单独的 npm 包,你可以只下载你想要的函数。

不举荐在项目中运用这种方法。首要,它并不像看起来相同轻量,lodash 中的公共代码会存在于每一个函数包中。其次,每个方法都是独立的依托包,意味着多次安装、多个 package.json 依托项、多个 node_modules 包目录。

4. 实用功用总结

Lodash 含有 Array, Collection, Date, Function, Lang, Math, Number, Object, String 等多个功用模块,总共几百个功用函数。官方文档上以字典顺序排序,不容易总结回想。这里总结一些 ES6 不容易完结的实用功用。

4.1. 字符串操作

4.1.1. 大小写转化

String.toLowerCase/toUpperCase 只能进行简单大小写转化,lodash 还供应了

_.lowerFirst(string);
_.upperFirst(string);
// 榜首个字符大写,其它字符小写
_.capitalize(string);

4.1.2. 命名风格转化

编程中,常见的多单词命名风格有:

  • 蛇形写法(snake case):单词之间用下划线联接,如 foo_bar
  • 烤肉串写法(kebab case):单词之间运用横线联接,如 foo-bar
  • 驼峰写法(camel case):从二个单词初步,每个单词的首字母大写,如 fooBar
  • 大驼峰写法(pascal case):每个单词首字母大写,如 FooBar

除了大驼峰,其他三种风格都有对应的转化函数:

_.snakeCase(string);
_.kebabCase(string);
_.camelCase(string);
// 使用upperFirst和camelCase 完结 pascalCase
const pascalCase = (string) => _.upperFirst(_.camelCase(string));
// examples
_.snakeCase("fooBar"); // 'foo_bar'
_.camelCase("Foo Bar"); // 'fooBar'
_.kebabCase("__FOO_BAR__"); // 'foo-bar'

其他,还有两个不常用的全大写和全小写写法(以空格为分隔符),它们与 _.toLower/toUpper 的区别是会辨认并转化字符串中的分隔符。

_.lowerCase(string);
_.upperCase(string);
// examples
_.lowerCase("--Foo-Bar--"); // 'foo bar'
_.upperCase("fooBar"); // 'FOO BAR'

4.2. 算术与数字

算术运算:

// 求总和
_.sum(array);
// 求平均值
_.mean(array);

常用的数字操作:

// 回来一个[lower,upper]之间的随机数
// 假设lower和upper中有浮点数,或许floating为true,回来浮点数,否则,回来整数
_.random(lower=0,upper=1 [,floating])
// 生成一个规划数组
_.range([start=0,]end,step=1)
// 把一个数字就近约束在某个区间内
_.clamp(number,[lower=0,] upper)
// examples
_.clamp(-10, -5, 5);    // -5
_.clamp(10, -5, 5);    // 5

4.3. 数组操作

4.3.1. 集结运算

// 交集 intersection
_.intersection(...arrays);
_.intersectionWith(...arrays [, comparator]);
_.intersectionBy(...arrays [, iteratee]);
// 并集
_.union(...arrays);
_.unionWith(...arrays [, comparator]);
_.unionBy(...arrays [, iteratee]);
// 集结差,A - B 表明属于集结A但不属于集结B的元素集结
_.difference(array, ...operands);
_.differenceWith(array, ...operands [, comparator]);
_.differenceBy(array, ...operands [, iteratee]);

这三簇函数都不改动原本的数组,而是回来一个新的数组作为运算效果。其间,交并集的运算效果不含重复元素,集结差取决于榜首个集结。

函数命名具有一定的规约,以交集为例:

  • intersection: 实行惯例运算,选用浅比较判别元素是否持平。
  • intersectionWidth: 调用 comparator 函数进行元素比较,可以自定义比较方法。
  • intersectionBy: 每个元素先通过 iteratee 函数处理,对转化后的数组进行比较运算,终究以转化前的榜首个元素为效果。
_.intersection([2, 1, 1], [2, 3], [2, 4]);
//=>[2]
const objects = [
  { x: 1, y: 2 },
  { x: 2, y: 1 },
];
const others = [
  { x: 1, y: 1 },
  { x: 1, y: 2 },
];
_.intersectionWith(objects, others, _.isEqual);
//=>[{'x':1,'y':2}]  , 效果引证objects中的元素
_.intersectionBy([2.1, 1.2], [2.3, 3.4], [3.2, 2.4], Math.floor);
//=>[2.1]

4.3.2. 分片/分区/分组

分片(chunk)是指把数组中的每 n 个元素分为一组(一片),假设不能整除,终究剩下的元素单独一片。

_.chunk(array [, size=1])
// example
_.chunk(['a','b','c','d','e'],2);
// => [["a", "b"], ["c", "d"], ["e"]]

分区(partition)是使用一个断言函数迭代每个元素,根据断言的 true 和 false,把元素分红两组。

_.partition(collection [, predicate])
// example
_.partition([4,5,6,7],num=>num>5)
// =>[[6, 7], [4, 5]]

分组(group) 则是用一个函数遍历每个元素,得到的效果作为该元素地点组的 key,相同 key 元素归为同一组。

_.groupBy(collection [, iteratee])
// example
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }

4.3.3. 有序数组查找/去重

在确保数组有序的情况下,查找和去重可以选用二分法,降低复杂度。Lodash 也供应了一些针对有序(升序)数组的操作。

sortedIndex / sortedLastIndex 可以操作根本的 number 数组和 string 数组:

// 回来刺进该元素后仍然能坚持数组有序的榜首个下标方位
_.sortedIndex(array, value);
// 类似 sortedIndex,但回来终究一个能坚持顺序的下标方位
_.sortedLastIndex(array, value);
// example
_.sortedIndex([1, 20, 20, 100, 500], 20); // 1
_.sortedLastIndex([1, 20, 20, 100, 500], 20); // 3

上面两个函数都只能在数字和字符串数组中运用,关于方针数组,可以用一个函数表明元素之间的排序根据:

// 以 iteratee 转化后的效果排序
_.sortedIndexBy(array, value [, iteratee])
_.sortedLastIndexBy(array, value [, iteratee])
// example
_.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, function(o) { return o.x; });
// => 0

留心,sortedIndex/sortedLastIndex 并不能直接用于元素查找,比如上面回来下标 3,但 array[3] 是 100 而不是 20。

有序数组查找用 sortedIndexOf/sortedLastIndexOf,它的功用与 indexOf/lastIndexOf 相同,不过选用了二分查找。

_.sortedIndexOf(array, value);
_.sortedLastIndexOf(array, value);
// example
_.sortedIndexOf([4, 5, 5, 5, 6], 5); //1

sortedUniq/sortedUniqBy 可以对有序数组去重。

_.sortedUniq(array)
_.sortedUniqBy(array [, iteratee])
// example
_.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
// => [1.1, 2.3]

4.3.4. 元素操作:取样/打乱/计数

// 随机回来一个元素
_.sample(collection)
// 随机回来n个元素
_.sampleSize(collection, [n=1])
// 打乱数组
_.shuffle(collection)
// 计数
_.countBy(collection [, iteratee])
_.countBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': 1, '6': 2 }

4.4. 方针操作

4.4.1. 方针转化

开发中常常需求从已有方针改造,得到我们期望的结构。Lodash 中有许多函数可以派上用场:

// 克隆
_.clone(value);
_.cloneWith(value, customizer);
_.cloneDeep(value);
_.cloneDeepWith(value, customizer);
// 同 Object.assign,把 sources 方针中的自有特色赋值到 object 中
_.assign(object, ...sources);
// 转化后赋值
_.assignWith(_.assignWith(object, sources, customizer));
// 类似_.assign, 但会赋值承继特色
_.assignIn(object, ...sources);
_.assignInWith(object, ...sources, customizer);
// 当object中不存在值时,才会赋值,常常用于兼并默许值
_.defaults(object, ...sources);
// _.default 不适用多层方针,需求运用_.defaultsDeep
_.defaultsDeep(object, ...sources);
// 兼并方针,类似 _.assign,但方针会递归深化,数组会被拼接
_.merge(object, ...sources);
_.mergeWith(object, ...sources);

留心,上面这些函数都会直接修改 object 参数。

与此不同,pick 和 omit 操作则回来新方针,不修改参数:

// 从方针中取出对应途径的值,组成一个新方针
_.pick(object, [paths]);
// 用一个断言函数决议要不要取这个特色,predicate(value,key)
_.pickBy(object, predicate);
// 去掉指定特色,把余下部分组成一个新方针,功用差于 pick
_.omit(object, [paths]);
_.omitBy(object, predicate);
// examples
_.pick({ a: 1, b: "2", c: 3 }, ["a", "c"]);
// => { 'a': 1, 'c': 3 }
_.pickBy({ a: 1, b: "2", c: 3 }, _.isNumber);
// => { 'a': 1, 'c': 3 }
_.omit({ a: 1, b: "2", c: 3 }, ["a", "c"]);
// => { 'b': '2' }

其他,方针还能像数组相同进行 map :

// iteratee(value,key,obj) 回来的效果作为新方针的key
_.mapKeys(object, iteratee);
// iteratee(value,key,obj) 回来的效果作为新方针的value
_.mapValues(object, iteratee);
// example
_.mapKeys({ a: 1, b: 2 }, function (value, key) {
  return key + value;
});
// => { 'a1': 1, 'b2': 2 }

4.4.2. 遍历方针

// 遍历自有特色,类似 for...in 加 hasOwnProperty判别。
_.forOwn(object, iteratee);
// 查找符合条件的key,类似数组的findIndex
_.findKey(object, iteratee);
_.findLastKey(object, iteratee);
// 查找方针中的函数特色
_.functions(object);
_.functionsIn(object);

4.4.3. 安全的 get/set

在 JavaScript 中,读取和设置某一个途径下的值,是不安全的:

const object={a:1};
const=object.b.someKey;   // TypeError: Cannot read properties of undefined
object.c.someKey=v;       // TypeError: Cannot set properties of undefined

Lodash 为我们供应了更安全的 get 和 set 操作:

// 当path对应的值不存在时,回来undefined,而不是报错。
_.get(object,path [, defaultValue]);
// 一层层set,而不是报错
_.set(object,path,value);
// 根据该途径现在的value,更新为updater回来后的值,updater(value)=>newValue
_.update(object,path,updater)

虽然最近的 可选链 “?.” 语法能替代 get 函数,但 set 操作仍然没有较好的原生支撑。

4.5. 函数操作

4.5.1. 控制函数实行

有时分,我们期望在特定条件下函数才实行,比如常见的防抖和节约:

  • 防抖(debounce):当函数调用时,等候一段时间再实行实际操作(内部函数),假设这段时间内函数再次被调用,则本次调用不实行实际操作,新调用从头初步等候。
  • 节约(throttle):一段时间内多次调用函数,只实行一次实际操作。
_.debounce(func [, wait=0] [, options={}])
_.throttle(func [, wait=0] [, options={}])
// examples
// 只会在停住之后从头布局
window.addEventListener('resize', _.debounce(calculateLayout, 150));
// 会继续更新方位,但150ms更新一次,避免卡顿
window.addEventListener('resize', _.throttle(updatePosition, 150));

也可以根据调用次数控制是否实行操作。

// 函数只调用一次
_.once(func);
// 只在前n次调用
_.before(func, n);
// 只在n次之后才调用
_.after(func, n);

推迟实行:

// 在本次调用仓库被清空后实行
_.defer(func, ...args);
// 等候wait ms 后实行,同setTimeout
_.delay(func, wait, ...args);

_.memorize 能缓存函数效果,避免重复核算,是一种常见的功用优化手法。

// resolver用于核算缓存key,当key相一起,运用缓存。默许运用func的榜首个参数为key
_.memorize(func [, resolver])

4.5.2. 函数参数转化

// 柯里化
_.curry(func, (arity = func.length));
// 绑定部分参数,但不绑定this
_.partial(func, ...args);
// 只接收前n个参数,忽略额定参数
_.ary(func, n);
// 只接收榜首个参数,同 _.ary(func,1)
_.unary(func);

4.6. 通用东西

流水线:

_.flow([funcs]);
const pascalCase = _.flow(_.upperFirst, _.camelCase);

生成一个仅有 ID:

_.uniqueId((prefix = ""));

参阅

  • Why the name ‘underscore’ or ‘lodash’? – Stack Overflow
  • javascript – Correct way to import lodash – Stack Overflow
  • Lodash cheatsheet