一、介绍


官方文档:

中文 - https://www.lodashjs.com/docs/latest

英文- https://lodash.com/docs/4.17.15

1、作用

lodash是一套工具库,内部封装了很多字符串、数组、对象等常见数据类型的处理函数。

2、组成

lodash.fp 暂不介绍(待写)

3、竞品比较

Lodash最初是 Underscore 的分支,后来逐渐壮大后自立门户。

Lodash 功能比 Underscore 更丰富,且 Underscore 已有3、4年没有更新,所以推荐使用 Loadash。

二、安装


1、browser

  1. <script src="lodash.js"></script>

CDN:

https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js

2、Node.js

npm i lodash

  1. // Load the full build.
  2. var _ = require('lodash');
  3. // Load the core build.
  4. var _ = require('lodash/core');
  5. // Load method categories.
  6. var array = require('lodash/array');
  7. // Load method.
  8. 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. // 1、difference
  2. _.difference([3, 2, 1], [4, 2]);
  3. // => [3, 1]
  4. // 2、differenceBy
  5. _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
  6. // => [3.1, 1.3]
  7. // 3、differenceWith
  8. var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
  9. _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
  10. // => [{ '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

  1. _.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. _([1, 2]).forEach(function(value) {
  2. console.log(value);
  3. });
  4. // => Logs `1` then `2`.
  5. _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
  6. console.log(key);
  7. });
  8. // => Logs 'a' then 'b' (iteration order is not guaranteed).
(5)遍历输出

map

invokeMap - this 相当于 map 的 item,此外还可以额外传入多个参数参与运算

  1. function square(item) {
  2. return item * item;
  3. }
  4. function invokeSquare(n, m) {
  5. return this * n * m;
  6. }
  7. re1 = _.map([1, 2], square);
  8. re2 = _.invokeMap([1, 2], invokeSquare, 2, 3);
  9. console.log(re1);
  10. // [ 1, 4 ]
  11. console.log(re2);
  12. // [ 6, 12 ]

还有类似 map 的 flatMap/flatMapDeep/flatMapDepth ,在 map 的基础上实现扁平化。

(6)聚合

countBy - 只算出现的次数

groupBy - 囊括出现的内容

keyBy - 自定义程度更高

(7)迭代归纳

reducetransform

注:transform 应该归属于 Object 章节而不是 Collection,但因为跟 reduce 用法挺像,所以这里放在一起做对比。

  1. _.reduce([2, 3, 4], function(sum, n) {
  2. return sum + n;    // return 值为下一次循环的 sum 值
  3. }, 1);     // 1 为初始值
  4. // => 10
  5. _.transform([2, 3, 4, 5 ,6], function(result, n) {
  6. result.push(n);
  7. return n < 4; // return false 即结束循环
  8. }, [1]);    // [1] 为初始值
  9. // => [ 1, 2, 3, 4 ]
(8)针对字符串

includes:_.includes('pebbles', 'eb'); 可以代替 indexOf

size:_.size('pebbles'); 可以代替 length

建议还是用原生方法。

拓展:用 es6 原生替代 lodash
  1. _.forEach([1, 2, 3], (i) => { console.log(i) })
  2. _.map([1, 2, 3], (i) => i + 1)
  3. _.filter([1, 2, 3], (i) => i > 1)
  4. _.reduce([1, 2, 3], (sum, i) => sum + i, 0)
  5. // 使用 ES6 改写
  6. [1, 2, 3].forEach((i) => { console.log(i) })
  7. [1, 2, 3].map((i) => i + 1)
  8. [1, 2, 3].filter((i) => i > 1)
  9. [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

  1. var abc = function(a, b, c) {
  2. return [a, b, c];
  3. };
  4. var curried = _.curry(abc);
  5. // 柯里化
  6. curried(1)(2)(3);
  7. // => [1, 2, 3]
  8. // 偏应用
  9. curried(1, 2)(3);
  10. // => [1, 2, 3]
  11. // 偏应用提供占位符
  12. curried(1)(_, 3)(2);
  13. // => [1, 2, 3]
  14. // 也可正常调用
  15. curried(1, 2, 3);
  16. // => [1, 2, 3]
(4)绑定上下文 or 偏应用

bind / bindKey - 既可绑定上下文,也可实现偏应用

partial / partialRight - 不可绑定上下文,仅实现偏应用 ( 即避免使用 .bind(null,…)

应用:

  • 给多个对象绑定共用函数

  • 给函数预先指定默认参数

  1. // bind - 不支持 bind 后修改 function 和 objcet
  2. var greet = function(greeting, punctuation) {
  3. return greeting + " " + this.user + punctuation;
  4. };
  5. var object = { user: "fred" };
  6. var bound = _.bind(greet, object, "hi");
  7. bound("!");
  8. // => 'hi fred!'
  9. // bind - 偏应用的占位符功能
  10. var bound = _.bind(greet, object, _, "!");
  11. bound("hi");
  12. // => 'hi fred!'
  13. // -----------------------------------
  14. // bindKey - 支持 bindKey 后修改 object 和(object 中的)function
  15. var object = {
  16. user: "fred",
  17. greet: function(greeting, punctuation) {
  18. return greeting + " " + this.user + punctuation;
  19. }
  20. };
  21. var bound = _.bindKey(object, "greet", "hi");
  22. bound("!");
  23. // => 'hi fred!'
  24. // bibindKeynd - 偏应用的占位符功能
  25. var bound = _.bindKey(object, "greet", _, "!");
  26. bound("hi");
  27. // => 'hi fred!'
  28. // -----------------------------------
  29. // partial
  30. var greet = function(greeting, name) {
  31. return greeting + " " + name;
  32. };
  33. var sayHelloTo = _.partial(greet, "hello");
  34. sayHelloTo("fred");
  35. // => 'hello fred'
  36. // partial - 偏应用的占位符功能
  37. var greetFred = _.partial(greet, _, "fred");
  38. greetFred("hi");
  39. // => 'hi fred'
(5)防止函数高频调用

debounce - 防抖动/防反跳,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。即将一定时间内的连续调用归为一个

应用:

  • 当窗口停止改变大小之后重新计算布局

  • 对用户输入的验证,避免在输入过程中处理,而是在停止输入后处理

  • 防止提交按钮被瞬间点击多次,导致大量的请求被重复发送

  1. jQuery(window).on('resize', _.debounce(calculateLayout, 150));
  2. // 取消一个 trailing 的防抖动调用
  3. jQuery(window).on('popstate', debounced.cancel);

throttle - 节流阀在 wait 秒内最多执行 func 一次的函数

应用:避免在页面滚动时频繁的更新定位

  1. jQuery(window).on('scroll', _.throttle(updatePosition, 100));
  2. // 取消一个 trailing 的节流调用。
  3. jQuery(window).on('popstate', throttled.cancel);

debounce 和 throttle 的区别:

debounce 和 throttle 都有 leading 和 trailing 的配置项。在都是默认值的情况下,使得这两个函数适用场景不一样,前者更多的是反抖,后者是节流。而当两者配置项相同的话,可以理解是一致的。

  1. var time_v = 2000;
  2. // debounce leading/trailing 的默认值
  3. _.debounce(() => console.log(_.random(10, 20)), time_v, {
  4. leading: false,
  5. trailing: true
  6. });
  7. // throttle leading/trailing 的默认值
  8. _.throttle(() => console.log(_.random(10, 20)), time_v, {
  9. leading: true,
  10. trailing: true
  11. });
(6)缓存结果

memoize - 缓存函数的计算结果

应用:缓存耗时较长的计算

  1. var object = { a: 1, b: 2 };
  2. var other = { c: 3, d: 4 };
  3. var values = _.memoize(_.values);
  4. // usage
  5. values(object);
  6. // => [1, 2]
  7. values(other);
  8. // => [3, 4]
  9. // 验证缓存是否生效
  10. object.a = 2;
  11. values(object);
  12. // => [1, 2] ( 证明把 object 的地址当成了缓存 key )
  13. // 修改缓存结果
  14. values.cache.set(object, [5, 6]);
  15. values(object);
  16. // => [ 5, 6 ]
  17. // 清除缓存
  18. values.cache.clear(object);
  19. values(object);
  20. // => [ 2, 2 ]
(7)翻转断言函数

negate

应用:可跟 filter 搭配

  1. function isEven(n) {
  2. return n % 2 == 0;
  3. }
  4. _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
  5. // => [1, 3, 5]

5、Lang

(1)拷贝

clone - 浅拷贝

cloneDeep - 深拷贝

  1. var objects = [{ a: 1 }, { b: 2 }];
  2. var shallow = _.clone(objects);
  3. console.log(shallow === objects);    //false
  4. console.log(shallow[0] === objects[0]);    //true
  5. var deep = _.cloneDeep(objects);
  6. console.log(deep[0] === objects[0]);    //false
(2)判断相等

eq - 浅比较

isEqual - 深比较

  1. var object = { 'a': 1 };
  2. var other = { 'a': 1 };
  3. _.eq(object, object);
  4. // => true
  5. _.eq(object, other);
  6. // => false
  7. _.isEqual(object, other);
  8. // => true
  9. object === other;
  10. // => 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 更严格一些

  1. _.toInteger("123das")
  2. // 0
  3. _.parseInt("123das")
  4. // 123
  5. Number("123das")
  6. // NaN

6、Math

7、Number

clamp - 返回限制在 lower 和 upper之间的值。

挺适合做 让接受参数落入合法区间。

random - 生成随机数,支持浮点。

8、Object

(1)对象合并

1、前者覆盖后者

defaults

defaultsDeep

  1. _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
  2. // => { 'a': 1, 'b': 2 }
  3. _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
  4. // => { 'a': { 'b': 2, 'c': 3 } }

2、后者覆盖前者

assign

assignIn - 包含原型链属性

  1. function Foo() {
  2. this.a = 1;
  3. }
  4. function Bar() {
  5. this.c = 3;
  6. }
  7. Foo.prototype.b = 2;
  8. Bar.prototype.d = 4;
  9. _.assign({ 'a': 0 }, new Foo, new Bar);
  10. // => { 'a': 1, 'c': 3 }
  11. _.assignIn({ 'a': 0 }, new Foo, new Bar);
  12. // => { '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 为对象时,则会递归合并这两个对象。

  1. _.assign({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
  2. // { a: { '3': 3 }, b: { '2': 2 } }
  3. _.merge({ a: { "1": 1 } }, { b: { "2": 2 } }, { a: { "3": 3 } });
  4. // { a: { '1': 1, '3': 3 }, b: { '2': 2 } }
(2)判断

conformsTo - 根据对象的 属性-值 判断

在 loadash 文档里,把 conformsTo 并没有归到 Object 目录下,而是放在 Lang。

  1. var object = { 'a': 1, 'b': 2 };
  2. _.conformsTo(object, { 'b': function(n) { return n > 1; } });
  3. // => true
  4. _.conformsTo(object, { 'b': function(n) { return n > 2; } });
  5. // => 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

  1. _.mapKeys({ a: 1, b: 2 }, function(value, key) {
  2. return key + value;
  3. });
  4. // => { a1: 1, b2: 2 }
  5. _.mapValues({ a: 1, b: 2 }, function(value, key) {
  6. return key + value;
  7. });
  8. // => { 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

  1. var object = { 'a': [{ 'b': { 'c': 3 } }] };
  2. _.get(object, 'a[0].b.c');
  3. // => 3
  4. _.get(object, ['a', '0', 'b', 'c']);
  5. // => 3
  6. _.get(object, 'a.b.c', 'default');
  7. // => 'default'
  1. var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
  2. re1 = _.result(object, 'a[0].b.c1');
  3. // 3
  4. var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
  5. re2 = _.invoke(object, 'a[0].b.c.slice', 1, 3);
  6. // [ 2, 3 ]
(6)取对象子集

pick - 正

omit - 反

  1. var object = { 'a': 1, 'b': '2', 'c': 3 };
  2. _.pick(object, ['a', 'c']);
  3. // => { 'a': 1, 'c': 3 }
  4. _.omit(object, ['a', 'c']);
  5. // => { 'b': '2' }

注意:如果对象有很多属性,pick/omit 会比较耗性能(因为属性会全部遍历),建议原生直接获取。

或者用 ES6 新特性 - 对象的解构赋值:

  1. const { a, c } = { a: 1, b: 2, c: 3 };
  2. 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 - 左右加符号

  1. _.pad('abc', 8);
  2. // => ' abc '
  3. _.pad('abc', 8, '_-');
  4. // => '_-abc_-_'
  5. _.pad('abc', 3);
  6. // => 'abc'

repeat - 重复加符号

  1. _.repeat('*', 3);
  2. // => '***'
  3. _.repeat('abc', 2);
  4. // => 'abcabc'
  5. _.repeat('abc', 0);
  6. // => ''
(3)截断显示

truncate - 截断 string 字符串,如果字符串超出了限定的最大值。 被截断的字符串后面会以 omission 代替,omission 默认是 "..."。

(4)转义

escape / unescape - 转义 string 中的 "&", "<", ">", '"', "'", 和 "`" 字符为 HTML实体字符

(5)模板

template提供了三种渲染模板

  • interpolate - <%= … %> 插入变量

  • escape - 如果您希望插入一个值, 并让其进行HTML转义,请使用<%- … %>

  • evaluate - <% … %> 执行任意的 JavaScript 代码

  1. // 1、使用 "interpolate" 分隔符创建编译模板
  2. var compiled = _.template('hello <%= user %>!');
  3. compiled({ 'user': 'fred' });
  4. // => 'hello fred!'
  5. // 1.1、使用 ES 分隔符代替默认的 "interpolate" 的 ERB 分隔符(ERB:嵌入式Ruby)
  6. var compiled = _.template('hello ${ user }!');
  7. compiled({ 'user': 'pebbles' });
  8. // => 'hello pebbles!'
  9. // 1.2 使用自定义的模板分隔符
  10. // 修改 _.templateSettings
  11. // 略
  12. // 2、使用 HTML "escape" 转义数据的值
  13. var compiled = _.template('<b><%- value %></b>');
  14. compiled({ 'value': '<script>' });
  15. // => '<b>&\lt;script&\gt;</b>'
  16. // 3、使用 "evaluate" 分隔符执行 JavaScript 和 生成HTML代码
  17. var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
  18. compiled({ 'users': ['fred', 'barney'] });
  19. // => '<li>fred</li><li>barney</li>'
  20. // ————————————————————————————————————————————
  21. // 使用反斜杠符号作为纯文本处理
  22. var compiled = _.template('<%= "\\<%- value %\\>" %>');
  23. compiled({ 'value': 'ignored' });
  24. // => '<%- value %>'
  25. // 使用 `imports` 选项导入 `jq` 作为 `jQuery` 的别名
  26. var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
  27. var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
  28. compiled({ 'users': ['fred', 'barney'] });
  29. // => '<li>fred</li><li>barney</li>'

template 还提供预编译的功能:

1、提高性能

2、方便调试(可使用 sourceURLs 提供错误的代码行号和堆栈跟踪)

预编译的详细用法 待写。

10、Util

(1)range - 生成范围

range/rangeRight - 用来创建整数灵活编号的列表的函数。

应用:

  • 便于 each 和 map 循环。

  • 模拟测试数据

  1. _.range(4);
  2. // => [0, 1, 2, 3]
  3. _.range(-4);
  4. // => [0, -1, -2, -3]
  5. _.range(1, 5);
  6. // => [1, 2, 3, 4]
  7. _.range(0, 20, 5);
  8. // => [0, 5, 10, 15]
  9. _.range(0, -4, -1);
  10. // => [0, -1, -2, -3]
  11. _.range(1, 4, 0);
  12. // => [1, 1, 1]
  13. _.range(0);
  14. // => []
(2)defaultTo - 返回默认值

defaultTo - 如果 value 为 NaN, null, undefined,那么返回 defaultValue(默认值)。

应用:替换非法值,保证程序可以顺畅往下执行。

  1. _.defaultTo(1, 10);
  2. // => 1
  3. _.defaultTo(undefined, 10);
  4. // => 10
(3)times - 屡次调用

调用函数 n 次,每次调用返回的结果存入到数组中

应用:

  • 快速模拟数据

  • 实现无参数循环

  1. _.times(4, _.constant(0));
  2. // => [0, 0, 0, 0]
(4)attempt

attempt - 调用 function,获得返回的结果或错误对象

应用:可替代写出来繁琐的 try-catch,如针对 JSON.parse。

  1. var elements = _.attempt(
  2. function(arr) {
  3. return arr[4].a ;
  4. },
  5. [1, 2, 3]
  6. );
  7. if (_.isError(elements)) {
  8. console.log(elements);
  9. }
(5)overEvery / overSome

overEvery /overSome

应用:校验参数格式合法性

  1. var func = _.overEvery([Boolean, isFinite]);
  2. func('1');
  3. // => true
  4. func(null);
  5. // => false
  6. func(NaN);
  7. // => false
  8. var func = _.overSome([Boolean, isFinite]);
  9. func('1');
  10. // => true
  11. func(null);
  12. // => true
  13. func(NaN);
  14. // => false
(6)cond

cond - 创建了一个函数,这个函数会迭代pairs(下面会介绍什么是pairs):依次执行pairs左值的函数,若返回 true 则返回 执行pairs右值的函数 的结果并结束;若返回 false 则继续往下,如果都是 false ,则最终返回 undefined。

应用:可以代替繁琐的 ifesle / switch 。

  1. var func = _.cond([
  2. [_.matches({ 'a': 1 }), _.constant('matches A')],
  3. [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
  4. // 最好有个这样的兜底,不然会返回undefined
  5. [_.stubTrue, _.constant('no match')]
  6. ]);
  7. func({ 'a': 1, 'b': 2 });
  8. // => 'matches A'
  9. func({ 'a': 0, 'b': 1 });
  10. // => 'matches B'
  11. func({ 'a': '1', 'b': '2' });
  12. // => 'no match'
拓展:什么是 pairs ?

pairs 是一种用数组描述数据的格式。

如对象 { 'fred': 30, 'barney': 40 } 可以表示为 [['fred', 30], ['barney', 40]] 。

lodash 提供了两个转换方法:

fromPairs

  1. _.fromPairs([['fred', 30], ['barney', 40]]);
  2. // => { 'fred': 30, 'barney': 40 }

toPairs

  1. function Foo() {
  2. this.a = 1;
  3. this.b = 2;
  4. }
  5. Foo.prototype.c = 3;
  6. _.toPairs(new Foo);
  7. // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
(7)flow - 连续调用

flow / flowRight - 每一个调用,传入的参数都是前一个函数返回的结果。

应用:通过 flow 对函数进行任意的组合,这样可以极大的增加函数的灵活性和复用性。

  1. let forA = function (a1, a2) {
  2. return Math.pow(a1 - a2, 2);
  3. };
  4. let dist = _.flow([
  5. function (x1, y1, x2, y2) {
  6. return forA(x1, x2) + forA(y1, y2)
  7. },
  8. Math.sqrt,
  9. Math.round
  10. ]);
  11. console.log(dist(10, 15, 90, 22)); // 80

flow 跟 下面介绍的 chain 有异曲同工之妙

(8)混入

mixin - 添加来源对象自身的所有可枚举函数属性到目标对象。(默认目标对象为 lodash 自身

还有一个跟 minxin 类似的 runInContext 函数,待写。

注:下面例子涉及到 链式操作,下面一节会详细介绍。

  1. // 开启链式操作(默认)
  2. _.mixin({
  3. capitalize_by_colin: function(string) {
  4. return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  5. }
  6. });
  7. re1 = _.capitalize_by_colin("fabio");
  8. re2 = _("fabio")
  9. .capitalize_by_colin()
  10. .value();
  11. console.log(re1);    // Fabio
  12. console.log(re2);    // Fabio
  13. // 关闭链式操作
  14. _.mixin(
  15. {
  16. capitalize_by_colin: function(string) {
  17. return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  18. }
  19. },
  20. { chain: false }
  21. );
  22. re3 = _.capitalize_by_colin("fabio");
  23. re4 = _("fabio").capitalize_by_colin();
  24. console.log(re3);    // Fabio
  25. console.log(re4);    // Fabio
(9)其他更多

1、为了跟 filter 等方法合作, lodash 创造了一些便捷方法:

  • matches

  • matchesProperty

  • property

上面支持 简写 和 iteratee 形式的缩写

  • conforms
  1. var users = [
  2. { user: "barney", age: _.constant(36), active: true },
  3. { user: "fred", age: _.constant(40), active: false }
  4. ];
  5. // origin
  6. console.log(
  7. _.filter(users, function(o) {
  8. return !o.active;
  9. })
  10. );
  11. // => objects for ['fred']
  12. // _.matches - 针对 对象 or 子对象
  13. console.log(_.filter(users, { user: "barney", active: true }));
  14. console.log(_.filter(users, _.iteratee({ user: "barney", active: true })));
  15. console.log(_.filter(users, _.matches({ user: "barney", active: true })));
  16. // => objects for ['barney']
  17. // _.matchesProperty - 针对 对象的单个属性和值
  18. console.log(_.filter(users, ["user", "fred"]));
  19. console.log(_.filter(users, _.iteratee(["user", "fred"])));
  20. console.log(_.filter(users, _.matchesProperty("user", "fred")));
  21. // => objects for ['fred']
  22. // _.property - 针对 对象的单个属性
  23. console.log(_.filter(users, "active"));
  24. console.log(_.filter(users, _.iteratee("active")));
  25. console.log(_.filter(users, _.property("active")));
  26. // => objects for ['barney']
  27. // _.conforms - 针对 对象的单个属性和值(更灵活)
  28. console.log(_.filter(users, _.conforms({ 'user': function(user) { return user === 'fred'; } })))
  29. // => objects for ['fred']

2、为了跟 map 等方法合作, lodash 创造了一些便捷方法:

  • method
  1. var users = [
  2. { user: "barney", age: _.constant(36), active: true },
  3. { user: "fred", age: _.constant(40), active: false }
  4. ];
  5. // _.method - 针对 对象的单个属性值(以函数的形式调用)
  6. console.log(_.map(users, _.method("age")));
  7. // => [ 36, 40 ]

上述介绍的 property 和 method 分别有相反的版本:propertyOfmethodOf

用法待写。

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. _([1, 2, 3]).head()
  3. // 注意:_([1, 2, 3]).head().reverse() 会报错,因为head()已经触发了自动结束。
  4. // 隐式链 - 需手动结束
  5. _([1, 2, 3])
  6. .reverse()
  7. .value();
  8. // ---------------------------------------------
  9. var users = [
  10. { 'user': 'barney', 'age': 36 },
  11. { 'user': 'fred', 'age': 40 }
  12. ];
  13. // 启用显式链 方法一
  14. _(users)
  15. .chain()
  16. .head()
  17. .pick('user')
  18. .value();
  19. // => { 'user': 'barney' }
  20. // 启用显式链 方法二
  21. _.chain(users)
  22. .head()
  23. .pick("user")
  24. .value();
  25. // => { 'user': 'barney' }
(5)处理中间结果

tap - 适合打印中间结果

thru - 适合修改中间结果

  1. 1tap - 直接修改值
  2. _([1, 2, 3])
  3. .tap(function(array) {
  4.    // 改变传入的数组
  5. array.pop();
  6. })
  7. .reverse()
  8. .value();
  9. // => [ 2, 1 ]
  10. 2thru - 需返回值
  11. _(' abc ')
  12. .chain()
  13. .trim()
  14. .thru(function(value) {
  15. return [value];
  16. })
  17. .value();
  18. // => ['abc']
(6)copy 链式队列(可换新值传入)

plant

  1. re = _.chain([1, 2, 3]).head();
  2. re2 = re4.plant([4, 5, 6]);
  3. re2.value();
  4. // => 4

对于 隐式链 - 自动结束 的链式队列,plant 会无计可施,建议转为显式的链式队列写法,再用plant。

四、总结


1、lodash 的优势

(1)支持函数式编程

函数式编程(functional programming)是一种将计算建模为表达式求值的编程风格。与命令式编程相反,命令式编程中的程序由在执行时更改全局状态的语句组成。函数式编程通常避免使用可变状态,而倾向于无副作用的函数和不可变数据。

lodash 提供了 lodash/fp ,可支持函数式编程。这个文章开头有介绍,不赘述。

类似的库还有 Ramda。

(2)支持按需加载

这个文章开头有介绍,不赘述。

(3)Immutable

相反叫mutable

lodash 的大多数方法都不会改变传入参数的原始对象,只会返回一个新的对象

但也有少部分例外是mutable的,例如:fill,pull,pullAll,pullAllBy,pullAllWith,pullAt,remove,reverse,assign,assignIn,assignInWith,assignWith,defaults,defaultsDeep,merge,mergeWith,set,setWith,unset,update,updateWith…

(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 插件

  1. 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

这篇文章介绍了原型污染的知识,感兴趣可以了解一下。

lodash 学习笔记的更多相关文章

  1. Lodash学习笔记

    有多年开发经验的工程师,往往都会有自己的一套工具库,称为utils.helpers等等,这套库一方面是自己的技术积累,另一方面也是对某项技术的扩展,领先于技术规范的制订和实现. Lodash就是这样的 ...

  2. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  3. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  4. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  5. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  6. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  7. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  8. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  9. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

随机推荐

  1. 基于python的selenium两种文件上传操作

    方法一.input标签上传     如果是input标签,可以直接输入路径,那么可以直接调用send_keys输入路径,这里不做过多赘述,前文有相关操作方法. 方法二.非input标签上传 这种上传方 ...

  2. charles 黑名单

    本文参考:charles 黑名单 charles 黑名单 功能:阻止对匹配HOST的请求:可以直接把请求丢掉,也可以直接返回403状态码: 我一般用黑名单工具来block一些软件的自动上传功能 黑名单 ...

  3. Tomcat启动时设置Jdk版本

    1. Window版本Tomcat 到bin下的setclasspath.bat文件,在文件的开始处添加如下代码: set JAVA_HOME=D:\Program Files\Java\jdk1.8 ...

  4. px、em、rem、%、vw、vh、vm这些单位的区别

    1.px px就是像素,也是我们现在经常使用的基本单位,比如常常听到的电脑像素是1024x768的,表示的是水平方向是1024个像素点,垂直方向是768个像素点. 2.em em参考物是父元素的fon ...

  5. 【Python爬虫】第四课(查询照片拍摄地址)

    首先,要能够查询到照片地址,查询的照片必须要开GPS拍,且上传时用原图…… 查询图片的exif信息,使用exifread包 import exifread img = exifread.process ...

  6. 浅谈 Vector

    目录 浅谈Vector 1.容器基本操作 2.vector 初始化 3.vector的赋值与swap 4.vector的增删改除 1.增加元素 2.访问元素 3.删除元素 4.元素的大小 浅谈Vect ...

  7. java数据结构——递归(Recursion)例题持续更新中

    继续学习数据结构递归,什么是递归呢?字面理解就是先递出去,然后回归,递归核心思想就是直接或间接调用本身,好比从前有座山,山里有位老和尚,在给小和尚讲故事,讲的是从前有座山,山里有位老和尚,在给小和尚讲 ...

  8. 使用docker安装mysql并连接

    1.查找镜像: docker search mysql 也可以去官网查看镜像tag,选择自己需要的版本,否则会下载最新版本:https://hub.docker.com/_/mysql/ 2.下载镜像 ...

  9. 标准io和管道练习

         标准IO和管道实验练习 [例1]把/etc/fstab文件内容重定向到/tmp目录下文件名为fstab.out 写法: 13:54:35 root@centos ~]#cat /etc/fs ...

  10. Microsoft Visual C++ 14.0 is required,成功解决这个问题!

    这个问题我向大家也不一定很好解决的,因为按照这个链接提示的打开,里面的t[mark][/mark]ools 页面早就已经不存在了,我也是看了网上各种各样的解决办法,解决起来是困难,这个提示的意思是缺少 ...