一、介绍
官方文档:
中文 - https://www.lodashjs.com/docs/latest
英文- https://lodash.com/docs/4.17.15
1、作用
lodash
是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数。
2、组成
lodash
:全部功能lodash.core
:只有核心的一些函数,详细见这儿https://github.com/lodash/lod...lodash.fp
:全部功能的函数式实现,文档见 https://github.com/lodash/lodash/wiki/FP-Guide
lodash.fp 暂不介绍(待写)
3、竞品比较
Lodash最初是 Underscore
的分支,后来逐渐壮大后自立门户。
Lodash 功能比 Underscore 更丰富,且 Underscore 已有3、4年没有更新,所以推荐使用 Loadash。
二、安装
1、browser
CDN:
https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
2、Node.js
npm i lodash
// Load the full build.
var _ = require('lodash');
// Load the core build.
var _ = require('lodash/core');
// Load method categories.
var array = require('lodash/array');
// Load method.
var chunk = require('lodash.chunk');
三、使用
注:本人装的是 latest 版本,_.VERSION
可查看版本号,为4.17.15
。
下面介绍的方法,是一些我认为属于重难点的、常用的。并有一些解释借鉴了 underscore 文档。
1、Array
(1)集合运算
intersection
- 交集
union
- 并集
difference
- ( A - B )
xor
- 只要一个元素出现两次及以上,则 remove 掉,其他的元素合并成一个新数组。
(2)difference
difference
- 没有第三个参数
differenceBy
- 第三个参数传的是 iteratee (value)
differenceWith
- 第三个参数传的是 comparator (arrVal, othVal)
// 1、difference
_.difference([3, 2, 1], [4, 2]);
// => [3, 1]
// 2、differenceBy
_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
// => [3.1, 1.3]
// 3、differenceWith
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
_.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
// => [{ 'x': 2, 'y': 1 }]
注:x、xBy、xWith 这三种模式在别的很多方法中也有体现。如 pullAll / pullAllBy / pullAllWith。
(3)drop
drop
- 从数组左边 remove 元素,可指定个数
dropRight
- 从数组右边 remove 元素,可指定个数
dropWhile
- 从数组左边 按条件 remove 元素,遇到条件不符合则终止
dropRightWhile
- 从数组右边 按条件 remove 元素,遇到条件不符合则终止
这里是 遇到条件不符合则终止,若想 遇到条件不符合不终止,也就没有左右之分,一律用 filter 替换即可。
注:x、xWhile 这两种模式在别的很多方法中也有体现。如 zip / zipWith。
(4)几种 删数组元素的方法
1、提供另一些 元素/数组/索引 来删除
without
(提供元素)- 不改变原数组
difference
(提供数组) - 不改变原数组
pull
(提供元素)/pullAll
(提供数组)/ pullAt
(提供索引)- 改变了原数组
2、单纯针对原数组去删除
filter
- 不改变原数组
remove
- 改变了原数组
所以 lodash 提供的方法也不都是 Immutable 的。
(5)remove 类空值
remove 掉: false
, null
, 0
, ""
, undefined
, 和 NaN
。
_.compact([0, 1, false, 2, '', 3]);// => [1, 2, 3]
2、Collection
集合函数能在数组,对象,和类数组对象,比如 arguments, NodeList 和类似的数据类型 (如 string) 上正常工作。
但是它通过鸭子类型
工作,所以要避免传递一个不固定length属性的对象。
拓展:什么是鸭子类型?
原话:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
所以鸭子类型关注点在对象的行为,而不是类型。
怎么有种 “不管黑猫白猫,抓住老鼠就是好猫” 的既视感。
(1)判断
every
- 全都符合返回 true
some
- 只要一条符合返回 true
注意:上面对空集合还是会返回 true。
(2)筛选
filter
- 正
reject
- 反
partition
- 同时输出正与反
(3)排序
sortBy
- 只能升序
orderBy
- 可升序可降序
(4)遍历
forEach
/ forEachRight
_([1, 2]).forEach(function(value) {
console.log(value);
});
// => Logs `1` then `2`.
_.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
console.log(key);
});
// => Logs 'a' then 'b' (iteration order is not guaranteed).
(5)遍历输出
map
invokeMap
- this 相当于 map 的 item,此外还可以额外传入多个参数参与运算
function square(item) {
return item * item;
}
function invokeSquare(n, m) {
return this * n * m;
}
re1 = _.map([1, 2], square);
re2 = _.invokeMap([1, 2], invokeSquare, 2, 3);
console.log(re1);
// [ 1, 4 ]
console.log(re2);
// [ 6, 12 ]
还有类似 map 的
flatMap
/flatMapDeep
/flatMapDepth
,在 map 的基础上实现扁平化。
(6)聚合
countBy
- 只算出现的次数
groupBy
- 囊括出现的内容
keyBy
- 自定义程度更高
(7)迭代归纳
reduce
和transform
注:transform 应该归属于 Object 章节而不是 Collection,但因为跟 reduce 用法挺像,所以这里放在一起做对比。
_.reduce([2, 3, 4], function(sum, n) {
return sum + n; // return 值为下一次循环的 sum 值
}, 1); // 1 为初始值
// => 10
_.transform([2, 3, 4, 5 ,6], function(result, n) {
result.push(n);
return n < 4; // return false 即结束循环
}, [1]); // [1] 为初始值
// => [ 1, 2, 3, 4 ]
(8)针对字符串
includes
:_.includes('pebbles', 'eb'); 可以代替 indexOf
size
:_.size('pebbles'); 可以代替 length
建议还是用原生方法。
拓展:用 es6 原生替代 lodash
_.forEach([1, 2, 3], (i) => { console.log(i) })
_.map([1, 2, 3], (i) => i + 1)
_.filter([1, 2, 3], (i) => i > 1)
_.reduce([1, 2, 3], (sum, i) => sum + i, 0)
// 使用 ES6 改写
[1, 2, 3].forEach((i) => { console.log(i) })
[1, 2, 3].map((i) => i + 1)
[1, 2, 3].filter((i) => i > 1)
[1, 2, 3].reduce((sum, i) => sum + i, 0)
3、Date
(1)获取时间
now
- 获取 unix 毫秒数
建议用原生的 Date.now() 。
4、Function
(1)函数调用的次数
after
- >=n 次后才能成功调用函数
应用:可以用作并行异步处理后的回调。
应用:可以用作次数限制。 应用:用过既废。 上面的方法,若函数不能执行,则返回最近一次的执行结果,如果没有执行过,则为 undefined。 例如一个函数虽然形参定义的是整形,但只能处理正整形。(即你只能传正整形才能正确调用)。 未提供的参数可用 偏应用和柯里化的区别:前者仅产生一个函数,后者可以产生多个函数。 上述概念,本是数学上的概念,但更多的用在了函数式编程上。 参考资料:http://songkun.me/2018/05/16/scala-partialfunction-partially-applied-function-currying/ 应用: 给多个对象绑定共用函数 给函数预先指定默认参数 应用: 当窗口停止改变大小之后重新计算布局 对用户输入的验证,避免在输入过程中处理,而是在停止输入后处理 防止提交按钮被瞬间点击多次,导致大量的请求被重复发送 应用:避免在页面滚动时频繁的更新定位 debounce 和 throttle 的区别: debounce 和 throttle 都有 leading 和 trailing 的配置项。在都是默认值的情况下,使得这两个函数适用场景不一样,前者更多的是反抖,后者是节流。而当两者配置项相同的话,可以理解是一致的。 应用:缓存耗时较长的计算 应用:可跟 filter 搭配 注意: 推荐使用这个而不是原生的isNaN(),因为会把 undefined 当成 true。 注意:javaScript 中数组和函数也是对象,所以: 首先,JavaScript 能够准确表示的整数范围在 于是 ES6 引入了 而 注意:验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。 对于object - 没有可枚举的属性 对于字符串和类数组对象,如果length属性为0 toInteger 更严格一些: 略 挺适合做 让接受参数落入合法区间。 1、前者覆盖后者 2、后者覆盖前者 注:x、xIn 这两种模式在别的很多方法中也有体现。如 functions / functionsIn 。 注:defaults 没有 defaultsIn ,assign 没有 assignDeep。 在 loadash 文档里,把 conformsTo 并没有归到 Object 目录下,而是放在 Lang。 注意:上述都无法保证遍历的顺序。 原生方法: 遍历自身可枚举属性(包含原型链): 遍历自身的可枚举属性: 之前在 Collection 分类里提到过 map,但在 Object 分类里,另有两个专属的 类map 方法: 原生方法: has = hasIn = 注意:如果对象有很多属性,pick/omit 会比较耗性能(因为属性会全部遍历),建议原生直接获取。 或者用 ES6 新特性 - 对象的解构赋值: 1、Camel case(驼峰) upper camel case CamelCase - TheQuickBrownFoxJumpsOverTheLazyDog (首字母大写) lower camel case camelCase - theQuickBrownFoxJumpsOverTheLazyDog(首字母小写) 2、Snake case (下划线) the_quick_brown_fox_jumps_over_the_lazy_dog (小写) UPPER_CASE_EMBEDDED_UNDERSCORE (大写)【常用做常量】 3、Kebab case (连字符) the-quick-brown-fox-jumps-over-the-lazy-dog(小写) TRAIN-CASE(大写) 4、Start case 5、Studly caps (大小写随机) interpolate - escape - 如果您希望插入一个值, 并让其进行HTML转义,请使用 evaluate - before
- 调用次数 once
- 只能调用一次函数
(2)延迟
delay
- 类似 setTimeoutdefer
- 类似延时为0的setTimeout。对于执行开销大的计算防止阻塞UI非常有用。扩展:什么是 偏函数 / 科里化 / 偏应用 ?
偏函数
:指的是仅能处理入参类型子集
的函数。偏应用
:例如在 Scala 中,当函数调用的参数不足时,编译器不会报错,而是先应用已提供的参数,并返回一个新函数,该函数接受原函数剩余未提供的参数作为自己的参数。
_
占位符表示。柯里化
:在偏应用基础上更进一步,将多参函数分解为一系列的单参函数,例如
curriedDivide(10)
调用第 1 个调用,并返回第 2 个函数curriedDivide(10)(2)
等于先后调用两个函数(3)柯里化 or 偏应用
curry
/curryRight
:var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
// 柯里化
curried(1)(2)(3);
// => [1, 2, 3]
// 偏应用
curried(1, 2)(3);
// => [1, 2, 3]
// 偏应用提供占位符
curried(1)(_, 3)(2);
// => [1, 2, 3]
// 也可正常调用
curried(1, 2, 3);
// => [1, 2, 3]
(4)绑定上下文 or 偏应用
bind
/ bindKey
- 既可绑定上下文,也可实现偏应用partial
/ partialRight
- 不可绑定上下文,仅实现偏应用 ( 即避免使用 .bind(null,…) )
// bind - 不支持 bind 后修改 function 和 objcet
var greet = function(greeting, punctuation) {
return greeting + " " + this.user + punctuation;
};
var object = { user: "fred" };
var bound = _.bind(greet, object, "hi");
bound("!");
// => 'hi fred!'
// bind - 偏应用的占位符功能
var bound = _.bind(greet, object, _, "!");
bound("hi");
// => 'hi fred!'
// -----------------------------------
// bindKey - 支持 bindKey 后修改 object 和(object 中的)function
var object = {
user: "fred",
greet: function(greeting, punctuation) {
return greeting + " " + this.user + punctuation;
}
};
var bound = _.bindKey(object, "greet", "hi");
bound("!");
// => 'hi fred!'
// bibindKeynd - 偏应用的占位符功能
var bound = _.bindKey(object, "greet", _, "!");
bound("hi");
// => 'hi fred!'
// -----------------------------------
// partial
var greet = function(greeting, name) {
return greeting + " " + name;
};
var sayHelloTo = _.partial(greet, "hello");
sayHelloTo("fred");
// => 'hello fred'
// partial - 偏应用的占位符功能
var greetFred = _.partial(greet, _, "fred");
greetFred("hi");
// => 'hi fred'
(5)防止函数高频调用
debounce
- 防抖动/防反跳
,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。即将一定时间内的连续调用归为一个。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
// 取消一个 trailing 的防抖动调用
jQuery(window).on('popstate', debounced.cancel);
throttle
- 节流阀
,在 wait 秒内最多执行 func 一次的函数。jQuery(window).on('scroll', _.throttle(updatePosition, 100));
// 取消一个 trailing 的节流调用。
jQuery(window).on('popstate', throttled.cancel);
var time_v = 2000;
// debounce leading/trailing 的默认值
_.debounce(() => console.log(_.random(10, 20)), time_v, {
leading: false,
trailing: true
});
// throttle leading/trailing 的默认值
_.throttle(() => console.log(_.random(10, 20)), time_v, {
leading: true,
trailing: true
});
(6)缓存结果
memoize
- 缓存函数的计算结果var object = { a: 1, b: 2 };
var other = { c: 3, d: 4 };
var values = _.memoize(_.values);
// usage
values(object);
// => [1, 2]
values(other);
// => [3, 4]
// 验证缓存是否生效
object.a = 2;
values(object);
// => [1, 2] ( 证明把 object 的地址当成了缓存 key )
// 修改缓存结果
values.cache.set(object, [5, 6]);
values(object);
// => [ 5, 6 ]
// 清除缓存
values.cache.clear(object);
values(object);
// => [ 2, 2 ]
(7)翻转断言函数
negate
function isEven(n) {
return n % 2 == 0;
}
_.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
// => [1, 3, 5]
5、Lang
(1)拷贝
clone
- 浅拷贝cloneDeep
- 深拷贝var objects = [{ a: 1 }, { b: 2 }];
var shallow = _.clone(objects);
console.log(shallow === objects); //false
console.log(shallow[0] === objects[0]); //true
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); //false
(2)判断相等
eq
- 浅比较isEqual
- 深比较var object = { 'a': 1 };
var other = { 'a': 1 };
_.eq(object, object);
// => true
_.eq(object, other);
// => false
_.isEqual(object, other);
// => true
object === other;
// => false
(3)判断类型
isArray
/isArrayLike
/isArrayLikeObject
- isArrayLikeObject = ArrayLike or Object
isArray
isArrayLike
isArrayLikeObject
[]
T
T
T
"123"
F
T
F
document.body.children
F
T
T
isElement
- DOM 元素。isError
isNil
- null or undefined。isNaN
- NaN
isObject
/isObjectLike
/isPlainObject
isObject
isObjectLike
isPlainObject
{}
T
T
T
[]
T
T
F
function(){}
T
F
F
isSafeInteger
- 基于 Number.isSafeInteger()拓展:什么是安全整数?
-2^53
到2^53
之间(不含两个端点),超过这个范围,无法精确表示这个值。Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()
则是用来判断一个整数是否落在这个范围之内。在 lodash 里,可以用isSafeInteger()
代替。
(4)判断空
isEmpty
(5)类型转换
toNumber
toInteger
toString
拓展:toInteger 跟 parseInt 的区别?
_.toInteger("123das")
// 0
_.parseInt("123das")
// 123
Number("123das")
// NaN
6、Math
7、Number
clamp
- 返回限制在 lower 和 upper之间的值。
random
- 生成随机数,支持浮点。8、Object
(1)对象合并
defaults
defaultsDeep
_.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
// => { 'a': 1, 'b': 2 }
_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
// => { 'a': { 'b': 2, 'c': 3 } }
assign
assignIn
- 包含原型链属性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 }
_.assignIn({ 'a': 0 }, new Foo, new Bar);
// => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
merge
- 类似 assign(后者覆盖前者),不同的是,defaults/assign 一碰到相同的 key 就去直接覆盖 value,而 merge 碰到相同的 key 且 value 为对象时,则会递归合并这两个对象。_.assign({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
// { a: { '3': 3 }, b: { '2': 2 } }
_.merge({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
// { a: { '1': 1, '3': 3 }, b: { '2': 2 } }
(2)判断
conformsTo
- 根据对象的 属性-值 判断
var object = { 'a': 1, 'b': 2 };
_.conformsTo(object, { 'b': function(n) { return n > 1; } });
// => true
_.conformsTo(object, { 'b': function(n) { return n > 2; } });
// => false
(3)遍历
forIn
/forInRight
- 遍历自身可枚举属性(包含原型链)forOwn
/forOwnRight
- 遍历自身的可枚举属性
for (let key in obj)
Object.keys(obj)
或 for (let key of Object.keys(obj))
(4)遍历输出
mapKeys
/mapValues
_.mapKeys({ a: 1, b: 2 }, function(value, key) {
return key + value;
});
// => { a1: 1, b2: 2 }
_.mapValues({ a: 1, b: 2 }, function(value, key) {
return key + value;
});
// => { a: 'a1', b: 'b2' }
(5)path 路径
has
/hasIn
- 判断 ( hasIn 包含原型链)get
/result
/invoke
- 获取(值)/调用(函数)【值本身就是函数】/调用(函数)【值不是函数,需自己提供函数+传参】set
/ update
/ unset
- 创建/更新/删除 (set = create or update)
object.hasOwnProperty(key)
"key" in object
var 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'
var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
re1 = _.result(object, 'a[0].b.c1');
// 3
var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
re2 = _.invoke(object, 'a[0].b.c.slice', 1, 3);
// [ 2, 3 ]
(6)取对象子集
pick
- 正omit
- 反var object = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
_.omit(object, ['a', 'c']);
// => { 'b': '2' }
const { a, c } = { a: 1, b: 2, c: 3 };
return { a, c };
9、String
(1)case styles
camelCase
- 转为驼峰写法kebabCase
- 转为 kebab case 写法扩展:几种 case styles
(2)适合打 log 的方法
pad
/ padEnd
/ padStart
- 左右加符号_.pad('abc', 8);
// => ' abc '
_.pad('abc', 8, '_-');
// => '_-abc_-_'
_.pad('abc', 3);
// => 'abc'
repeat
- 重复加符号_.repeat('*', 3);
// => '***'
_.repeat('abc', 2);
// => 'abcabc'
_.repeat('abc', 0);
// => ''
(3)截断显示
truncate
- 截断 string 字符串,如果字符串超出了限定的最大值。 被截断的字符串后面会以 omission 代替,omission 默认是 "..."。(4)转义
escape
/ unescape
- 转义 string 中的 "&", "<", ">", '"', "'", 和 "`" 字符为 HTML实体字符。(5)模板
template
提供了三种渲染模板
:
<%= … %>
插入变量<%- … %>
<% … %>
执行任意的 JavaScript 代码// 1、使用 "interpolate" 分隔符创建编译模板
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'
// 1.1、使用 ES 分隔符代替默认的 "interpolate" 的 ERB 分隔符(ERB:嵌入式Ruby)
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'
// 1.2 使用自定义的模板分隔符
// 修改 _.templateSettings
// 略
// 2、使用 HTML "escape" 转义数据的值
var compiled = _.template('<%- value %>');
compiled({ 'value': '