lodash 学习笔记

一、介绍


官方文档:

中文 - 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)迭代归纳

reducetransform

注: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 - 调用次数

应用:可以用作次数限制。

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^532^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

于是 ES6 引入了Number.MAX_SAFE_INTEGERNumber.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(后者覆盖前者),不同的是,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 - 根据对象的 属性-值 判断

在 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('<%- value %>');
compiled({ 'value': '
                    
                    

你可能感兴趣的:(lodash 学习笔记)