一、介绍
官方文档:
中文 - 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
<script src="lodash.js"></script>
CDN:
https://cdn.jsdelivr.net/npm/lodash@4.17.15/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 次后才能成功调用函数
应用:可以用作并行异步处理后的回调。
before
- 调用次数 <n 次
应用:可以用作次数限制。
once
- 只能调用一次函数
应用:用过既废。
上面的方法,若函数不能执行,则返回最近一次的执行结果,如果没有执行过,则为 undefined。
(2)延迟
delay
- 类似 setTimeout
defer
- 类似延时为0的setTimeout。对于执行开销大的计算防止阻塞UI非常有用。
扩展:什么是 偏函数 / 科里化 / 偏应用 ?
- 偏函数:partial function
- 部分应用函数(偏应用):partially applied function
- 柯里化:currying
偏函数
:指的是仅能处理入参类型子集
的函数。
例如一个函数虽然形参定义的是整形,但只能处理正整形。(即你只能传正整形才能正确调用)。
偏应用
:例如在 Scala 中,当函数调用的参数不足时,编译器不会报错,而是先应用已提供的参数,并返回一个新函数,该函数接受原函数剩余未提供的参数作为自己的参数。
未提供的参数可用
_
占位符表示。
柯里化
:在偏应用基础上更进一步,将多参函数分解为一系列的单参函数,例如
curriedDivide(10)
调用第 1 个调用,并返回第 2 个函数curriedDivide(10)(2)
等于先后调用两个函数
偏应用和柯里化的区别:前者仅产生一个函数,后者可以产生多个函数。
上述概念,本是数学上的概念,但更多的用在了函数式编程上。
参考资料:http://songkun.me/2018/05/16/scala-partialfunction-partially-applied-function-currying/
(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);
debounce 和 throttle 的区别:
debounce 和 throttle 都有 leading 和 trailing 的配置项。在都是默认值的情况下,使得这两个函数适用场景不一样,前者更多的是反抖,后者是节流。而当两者配置项相同的话,可以理解是一致的。
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
应用:可跟 filter 搭配
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
推荐使用这个而不是原生的isNaN(),因为会把 undefined 当成 true。
isObject
/isObjectLike
/isPlainObject
注意:javaScript 中数组和函数也是对象,所以:
isObject isObjectLike isPlainObject {} T T T [] T T F function(){} T F F
isSafeInteger
- 基于 Number.isSafeInteger()
拓展:什么是安全整数?
首先,JavaScript 能够准确表示的整数范围在-2^53
到2^53
之间(不含两个端点),超过这个范围,无法精确表示这个值。
于是 ES6 引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限。
而Number.isSafeInteger()
则是用来判断一个整数是否落在这个范围之内。在 lodash 里,可以用isSafeInteger()
代替。
注意:验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。
(4)判断空
isEmpty
对于object - 没有可枚举的属性
对于字符串和类数组对象,如果length属性为0
(5)类型转换
toNumber
toInteger
toString
拓展:toInteger 跟 parseInt 的区别?
toInteger 更严格一些:
_.toInteger("123das") // 0 _.parseInt("123das") // 123 Number("123das") // NaN
6、Math
略
7、Number
clamp
- 返回限制在 lower 和 upper之间的值。
挺适合做 让接受参数落入合法区间。
random
- 生成随机数,支持浮点。
8、Object
(1)对象合并
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 } }
2、后者覆盖前者
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 }
注:x、xIn 这两种模式在别的很多方法中也有体现。如 functions / functionsIn 。
注:defaults 没有 defaultsIn ,assign 没有 assignDeep。
merge
- 类似 assign,不同的是如果碰到相同的属性且属性值为对象,则会递归合并。
_.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
- 根据对象的 属性-值 判断
在 loadash 文档里,把 conformsTo 并没有归到 Object 目录下,而是放在 Lang。
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)遍历输出
之前在 Collection 分类里提到过 map,但在 Object 分类里,另有两个专属的 类map 方法:
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)
原生方法:
has =
object.hasOwnProperty(key)
hasIn =
"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' }
注意:如果对象有很多属性,pick/omit 会比较耗性能(因为属性会全部遍历),建议原生直接获取。
或者用 ES6 新特性 - 对象的解构赋值:
const { a, c } = { a: 1, b: 2, c: 3 }; return { a, c };
9、String
(1)case styles
camelCase
- 转为驼峰写法
kebabCase
- 转为 kebab case 写法
扩展:几种 case styles
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
- Foo Bar
5、Studly caps (大小写随机)
- tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG
(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
提供了三种渲染模板
:
interpolate -
<%= … %>
插入变量escape - 如果您希望插入一个值, 并让其进行HTML转义,请使用
<%- … %>
evaluate -
<% … %>
执行任意的 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('<b><%- value %></b>'); compiled({ 'value': '<script>' }); // => '<b>&\lt;script&\gt;</b>' // 3、使用 "evaluate" 分隔符执行 JavaScript 和 生成HTML代码 var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'); compiled({ 'users': ['fred', 'barney'] }); // => '<li>fred</li><li>barney</li>' // ———————————————————————————————————————————— // 使用反斜杠符号作为纯文本处理 var compiled = _.template('<%= "\\<%- value %\\>" %>'); compiled({ 'value': 'ignored' }); // => '<%- value %>' // 使用 `imports` 选项导入 `jq` 作为 `jQuery` 的别名 var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>'; var compiled = _.template(text, { 'imports': { 'jq': jQuery } }); compiled({ 'users': ['fred', 'barney'] }); // => '<li>fred</li><li>barney</li>'
template 还提供预编译
的功能:
1、提高性能
2、方便调试(可使用 sourceURLs 提供错误的代码行号和堆栈跟踪)
预编译的详细用法 待写。
10、Util
(1)range - 生成范围
range
/rangeRight
- 用来创建整数灵活编号的列表的函数。
应用:
便于 each 和 map 循环。
模拟测试数据
_.range(4); // => [0, 1, 2, 3] _.range(-4); // => [0, -1, -2, -3] _.range(1, 5); // => [1, 2, 3, 4] _.range(0, 20, 5); // => [0, 5, 10, 15] _.range(0, -4, -1); // => [0, -1, -2, -3] _.range(1, 4, 0); // => [1, 1, 1] _.range(0); // => []
(2)defaultTo - 返回默认值
defaultTo
- 如果 value 为 NaN, null, undefined,那么返回 defaultValue(默认值)。
应用:替换非法值,保证程序可以顺畅往下执行。
_.defaultTo(1, 10); // => 1 _.defaultTo(undefined, 10); // => 10
(3)times - 屡次调用
调用函数 n 次,每次调用返回的结果存入到数组中
应用:
快速模拟数据
实现无参数循环
_.times(4, _.constant(0)); // => [0, 0, 0, 0]
(4)attempt
attempt
- 调用 function,获得返回的结果或错误对象。
应用:可替代写出来繁琐的 try-catch,如针对 JSON.parse。
var elements = _.attempt( function(arr) { return arr[4].a ; }, [1, 2, 3] ); if (_.isError(elements)) { console.log(elements); }
(5)overEvery / overSome
overEvery
/overSome
应用:校验参数格式合法性
var func = _.overEvery([Boolean, isFinite]); func('1'); // => true func(null); // => false func(NaN); // => false var func = _.overSome([Boolean, isFinite]); func('1'); // => true func(null); // => true func(NaN); // => false
(6)cond
cond
- 创建了一个函数,这个函数会迭代pairs
(下面会介绍什么是pairs):依次执行pairs左值的函数,若返回 true 则返回 执行pairs右值的函数 的结果并结束;若返回 false 则继续往下,如果都是 false ,则最终返回 undefined。
应用:可以代替繁琐的 ifesle / switch 。
var func = _.cond([ [_.matches({ 'a': 1 }), _.constant('matches A')], [_.conforms({ 'b': _.isNumber }), _.constant('matches B')], // 最好有个这样的兜底,不然会返回undefined [_.stubTrue, _.constant('no match')] ]); func({ 'a': 1, 'b': 2 }); // => 'matches A' func({ 'a': 0, 'b': 1 }); // => 'matches B' func({ 'a': '1', 'b': '2' }); // => 'no match'
拓展:什么是 pairs ?
pairs
是一种用数组描述数据的格式。
如对象 { 'fred': 30, 'barney': 40 } 可以表示为 [['fred', 30], ['barney', 40]] 。
lodash 提供了两个转换方法:
fromPairs
_.fromPairs([['fred', 30], ['barney', 40]]); // => { 'fred': 30, 'barney': 40 }
toPairs
function Foo() { this.a = 1; this.b = 2; } Foo.prototype.c = 3; _.toPairs(new Foo); // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
(7)flow - 连续调用
flow
/ flowRight
- 每一个调用,传入的参数都是前一个函数返回的结果。
应用:通过 flow 对函数进行任意的组合,这样可以极大的增加函数的灵活性和复用性。
let forA = function (a1, a2) { return Math.pow(a1 - a2, 2); }; let dist = _.flow([ function (x1, y1, x2, y2) { return forA(x1, x2) + forA(y1, y2) }, Math.sqrt, Math.round ]); console.log(dist(10, 15, 90, 22)); // 80
flow 跟 下面介绍的 chain 有异曲同工之妙。
(8)混入
mixin
- 添加来源对象自身的所有可枚举函数属性到目标对象。(默认目标对象为 lodash 自身)
还有一个跟 minxin 类似的 runInContext 函数,待写。
注:下面例子涉及到 链式操作,下面一节会详细介绍。
// 开启链式操作(默认) _.mixin({ capitalize_by_colin: function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }); re1 = _.capitalize_by_colin("fabio"); re2 = _("fabio") .capitalize_by_colin() .value(); console.log(re1); // Fabio console.log(re2); // Fabio // 关闭链式操作 _.mixin( { capitalize_by_colin: function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }, { chain: false } ); re3 = _.capitalize_by_colin("fabio"); re4 = _("fabio").capitalize_by_colin(); console.log(re3); // Fabio console.log(re4); // Fabio
(9)其他更多
1、为了跟 filter 等方法合作, lodash 创造了一些便捷方法:
matches
matchesProperty
property
上面支持 简写 和 iteratee 形式的缩写
conforms
var users = [ { user: "barney", age: _.constant(36), active: true }, { user: "fred", age: _.constant(40), active: false } ]; // origin console.log( _.filter(users, function(o) { return !o.active; }) ); // => objects for ['fred'] // _.matches - 针对 对象 or 子对象 console.log(_.filter(users, { user: "barney", active: true })); console.log(_.filter(users, _.iteratee({ user: "barney", active: true }))); console.log(_.filter(users, _.matches({ user: "barney", active: true }))); // => objects for ['barney'] // _.matchesProperty - 针对 对象的单个属性和值 console.log(_.filter(users, ["user", "fred"])); console.log(_.filter(users, _.iteratee(["user", "fred"]))); console.log(_.filter(users, _.matchesProperty("user", "fred"))); // => objects for ['fred'] // _.property - 针对 对象的单个属性 console.log(_.filter(users, "active")); console.log(_.filter(users, _.iteratee("active"))); console.log(_.filter(users, _.property("active"))); // => objects for ['barney'] // _.conforms - 针对 对象的单个属性和值(更灵活) console.log(_.filter(users, _.conforms({ 'user': function(user) { return user === 'fred'; } }))) // => objects for ['fred']
2、为了跟 map 等方法合作, lodash 创造了一些便捷方法:
method
var users = [ { user: "barney", age: _.constant(36), active: true }, { user: "fred", age: _.constant(40), active: false } ]; // _.method - 针对 对象的单个属性值(以函数的形式调用) console.log(_.map(users, _.method("age"))); // => [ 36, 40 ]
上述介绍的 property 和 method 分别有相反的版本:
propertyOf
和methodOf
。用法待写。
11、Seq
(1)创建链对象
1、通过_(value)
建立了一个隐式链对象
2、通过_.chain(value)
建立了一个显式链对象
3、也可通过_(value).chain()
从隐式转成显式。
(2)显式链 (Explicit Chaining) / 隐式链 (Implicit Chaining) 区别
显式链调用
的话,需要通过commit()
手动结束链式反应,或者 value()
手动结束链式反应并提取值。
隐式链调用
的话,碰到能返回唯一值 (single value) 或原生数据类型(primitive value),才会自动结束链式反应并自动提取值。否则需要你像上面一样手动操作。
例如 sum 可以触发隐式链调用的自动结束,但是 filter 不行。
什么时候用显式/隐式?
显式对 commit 和 value 更可控,灵活度更高。
隐式可以简洁代码。
(3)链式(队列)调用 与 Lazy evaluation(惰性计算)
链式队列
调用的过程中,可以把很多操作串起来,然后一起做 Lazy evaluation
(惰性计算),这中间会允许一些方法 shortcut fusion
。
shortcut fusion 是一种通过合并链式 iteratee 调用从而大大降低迭代的次数以提高执行性能的方式。
所以推荐使用显式链调用,这样可以可控的、最大化的利用 Lazy evaluation。
注意:但也要谨慎创建链对象,因为会导致高内存使用率,从而降低性能。
但 lodash 有些方法不支持链式调用,如 reduce。详细如下:
支持 链式调用 的方法: after, ary, assign, assignIn, assignInWith, assignWith, at, before, bind, bindAll, bindKey, castArray, chain, chunk, commit, compact, concat, conforms, constant, countBy, create, curry, debounce, defaults, defaultsDeep, defer, delay, difference, differenceBy, differenceWith, drop, dropRight, dropRightWhile, dropWhile, extend, extendWith, fill, filter, flatMap, flatMapDeep, flatMapDepth, flatten, flattenDeep, flattenDepth, flip, flow, flowRight, fromPairs, functions, functionsIn, groupBy, initial, intersection, intersectionBy, intersectionWith, invert, invertBy, invokeMap, iteratee, keyBy, keys, keysIn, map, mapKeys, mapValues, matches, matchesProperty, memoize, merge, mergeWith, method, methodOf, mixin, negate, nthArg, omit, omitBy, once, orderBy, over, overArgs, overEvery, overSome, partial, partialRight, partition, pick, pickBy, plant, property, propertyOf, pull, pullAll, pullAllBy, pullAllWith, pullAt, push, range, rangeRight, rearg, reject, remove, rest, reverse, sampleSize, set, setWith, shuffle, slice, sort, sortBy, splice, spread, tail, take, takeRight, takeRightWhile, takeWhile, tap, throttle, thru, toArray, toPairs, toPairsIn, toPath, toPlainObject, transform, unary, union, unionBy, unionWith, uniq, uniqBy, uniqWith, unset, unshift, unzip, unzipWith, update, updateWith, values, valuesIn, without, wrap, xor, xorBy, xorWith, zip, zipObject, zipObjectDeep, and zipWith。
默认 不支持 链式调用 的方法: add, attempt, camelCase, capitalize, ceil, clamp, clone, cloneDeep, cloneDeepWith, cloneWith, conformsTo, deburr, defaultTo, divide, each, eachRight, endsWith, eq, escape, escapeRegExp, every, find, findIndex, findKey, findLast, findLastIndex, findLastKey, first, floor, forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, get, gt, gte, has, hasIn, head, identity, includes, indexOf, inRange, invoke, isArguments, isArray, isArrayBuffer, isArrayLike, isArrayLikeObject, isBoolean, isBuffer, isDate, isElement, isEmpty, isEqual, isEqualWith, isError, isFinite, isFunction, isInteger, isLength, isMap, isMatch, isMatchWith, isNaN, isNative, isNil, isNull, isNumber, isObject, isObjectLike, isPlainObject, isRegExp, isSafeInteger, isSet, isString, isUndefined, isTypedArray, isWeakMap, isWeakSet, join, kebabCase, last, lastIndexOf, lowerCase, lowerFirst, lt, lte, max, maxBy, mean, meanBy, min, minBy, multiply, noConflict, noop, now, nth, pad, padEnd, padStart, parseInt, pop, random, reduce, reduceRight, repeat, result, round, runInContext, sample, shift, size, snakeCase, some, sortedIndex, sortedIndexBy, sortedLastIndex, sortedLastIndexBy, startCase, startsWith, stubArray, stubFalse, stubObject, stubString, stubTrue, subtract, sum, sumBy, template, times, toFinite, toInteger, toJSON, toLength, toLower, toNumber, toSafeInteger, toString, toUpper, trim, trimEnd, trimStart, truncate, unescape, uniqueId, upperCase, upperFirst, value, and words。
(4)demo
// 隐式链 - 自动结束 _([1, 2, 3]).head() // 注意:_([1, 2, 3]).head().reverse() 会报错,因为head()已经触发了自动结束。 // 隐式链 - 需手动结束 _([1, 2, 3]) .reverse() .value(); // --------------------------------------------- var users = [ { 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 } ]; // 启用显式链 方法一 _(users) .chain() .head() .pick('user') .value(); // => { 'user': 'barney' } // 启用显式链 方法二 _.chain(users) .head() .pick("user") .value(); // => { 'user': 'barney' }
(5)处理中间结果
tap
- 适合打印中间结果
thru
- 适合修改中间结果
1、tap - 直接修改值 _([1, 2, 3]) .tap(function(array) { // 改变传入的数组 array.pop(); }) .reverse() .value(); // => [ 2, 1 ] 2、thru - 需返回值 _(' abc ') .chain() .trim() .thru(function(value) { return [value]; }) .value(); // => ['abc']
(6)copy 链式队列(可换新值传入)
plant
re = _.chain([1, 2, 3]).head(); re2 = re4.plant([4, 5, 6]); re2.value(); // => 4
对于 隐式链 - 自动结束 的链式队列,plant 会无计可施,建议转为显式的链式队列写法,再用plant。
四、总结
1、lodash 的优势
(1)支持函数式编程
函数式编程(functional programming)
是一种将计算建模为表达式求值的编程风格。与命令式编程
相反,命令式编程中的程序由在执行时更改全局状态的语句组成。函数式编程通常避免使用可变状态,而倾向于无副作用的函数和不可变数据。
lodash 提供了 lodash/fp ,可支持函数式编程。这个文章开头有介绍,不赘述。
类似的库还有 Ramda。
(2)支持按需加载
这个文章开头有介绍,不赘述。
(3)Immutable
相反叫mutable
所有的方法都不会改变传入参数的原始对象,只会返回一个新的对象。
但也有特例,比如 remove 方法。
(4)Compose(组合)
通过 flow,这个在上文有介绍,不赘述。
(5)lazy evaluation(惰性求值)
这个在 Sqe 章节有介绍,不赘述。
提一句,flow 也支持 lazy evaluation。
(6)null-safe
各种方法普遍对 null
值容错。
(7)不侵入原型
拓展:市面上的 js 工具库有几派做法:
1、支持直接在原始类的 prototype 上直接扩展,以 sugar.js 和 prototype.js 为典型;
2、严格禁止在 prototype 上直接扩展,以 underscore 和 lodash 为典型;
3、中间派,先判断 prototype 是否有重名的,已经有了就不加了,以 es6-shim.js 为代表,使 ES6 能兼容于老浏览器。
但越来越多的实践表明,不推荐在 prototype 上拓展函数。原因是:
1、容易冲突
跟别人或者干脆跟官方更新的函数名冲突。
最著名的例子就是上面介绍的 prototype.js 库,在 prototype 上拓展了一个叫 getElementsByClassName 的函数,返回的是 Array,结果后来 js 官方也更新了个getElementsByClassName 函数,返回的却是 NodeList。这就冲突了。
后来 prototype.js 被迫给出了解决方案:https://johnresig.com/blog/getelementsbyclassname-pre-prototype-16/,感兴趣的可以看看。
2、性能较差
所以还是推荐使用 underscore / lodash 这样的工具库。
2、ES6(原生)vs lodash ?
问1:lodash 的好处,上面都提到了,那到底什么时候用原生方法?
答:建议能用原生就用原生,因为原生的性能更高。
问2:那有没有什么方法可以快速判断有没有原生方法支持呢?
答:有。
方法一:安装 eslint 插件
npm install --save-dev eslint-plugin-you-dont-need-lodash-underscore
方法二:查阅这个别人整理的挺全的文档
https://segmentfault.com/a/1190000004460234#articleHeader48
3、容我吐槽下官方文档
编排的顺序是按照字母顺序而不是逻辑顺序
(有部分是上一条的原因)例子不友好,一上来就是用还没看到的方法
没有 underscore 文档详细(导致我这篇 blog 其实参考了 [underscore 文档](https://www.bootcss.com/p/underscore/ 互为补充)
五、拓展
《Lodash 严重安全漏洞背后 你不得不知道的 JavaScript 知识》:https://zhuanlan.zhihu.com/p/74625177
这篇文章介绍了原型污染的知识,感兴趣可以了解一下。