Lodash入门介绍

原文额地址  http://www.w3cplus.com/javascript/lodash-intro.htmljavascript

有多年开发经验的工程师,每每都会有本身的一套工具库,称为 utils、helpers 等等,这套库一方面是本身的技术积累,另外一方面也是对某项技术的扩展,领先于技术规范的制定和实现。php

Lodash 就是这样的一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前 ECMAScript 还没有制定的规范,但同时被业界所承认的辅助函数。目前天天使用 npm 安装 Lodash 的数量在百万级以上,这在必定程度上证实了其代码的健壮性,值得咱们在项目中一试。html

模块组成

Lodash 提供的辅助函数主要分为如下几类,函数列表和用法实例请查看 Lodash 的官方文档前端

  • Array,适用于数组类型,好比填充数据、查找元素、数组分片等操做
  • Collection,适用于数组和对象类型,部分适用于字符串,好比分组、查找、过滤等操做
  • Function,适用于函数类型,好比节流、延迟、缓存、设置钩子等操做
  • Lang,广泛适用于各类类型,经常使用于执行类型判断和类型转换
  • Math,适用于数值类型,经常使用于执行数学运算
  • Number,适用于生成随机数,比较数值与数值区间的关系
  • Object,适用于对象类型,经常使用于对象的建立、扩展、类型转换、检索、集合等操做
  • Seq,经常使用于建立链式调用,提升执行性能(惰性计算)
  • String,适用于字符串类型

lodash/fp 模块提供了更接近函数式编程的开发方式,其内部的函数通过包装,具备 immutable、auto-curried、iteratee-first、data-last(官方介绍)等特色。Lodash 在 GitHub Wiki 中对 lodash/fp 的特色作了以下概述:java

  • Fixed Arity,固化参数个数,便于柯里化
  • Rearragned Arguments,从新调整参数位置,便于函数之间的聚合
  • Capped Iteratee Argument,封装 Iteratee 参数
  • New Methods

In functional programming, an iteratee is a composable abstraction for incrementally processing sequentially presented chunks of input data in a purely functional fashion. With iteratees, it is possible to lazily transform how a resource will emit data, for example, by converting each chunk of the input to uppercase as they are retrieved or by limiting the data to only the five first chunks without loading the whole input data into memory. Iteratees are also responsible for opening and closing resources, providing predictable resource management. ———— iteratee, wikipediapython

// The `lodash/map` iteratee receives three arguments: // (value, index|key, collection) _.map(['6', '8', '10'], parseInt); // → [6, NaN, 2] // The `lodash/fp/map` iteratee is capped at one argument: // (value) fp.map(parseInt)(['6', '8', '10']); // → [6, 8, 10] // `lodash/padStart` accepts an optional `chars` param. _.padStart('a', 3, '-') // → '--a' // `lodash/fp/padStart` does not. fp.padStart(3)('a'); // → ' a' fp.padCharsStart('-')(3)('a'); // → '--a' // `lodash/filter` is data-first iteratee-last: // (collection, iteratee) var compact = _.partial(_.filter, _, Boolean); compact(['a', null, 'c']); // → ['a', 'c'] // `lodash/fp/filter` is iteratee-first data-last: // (iteratee, collection) var compact = fp.filter(Boolean); compact(['a', null, 'c']); // → ['a', 'c'] 

在 React + Webpack + Babel(ES6) 的开发环境中,使用 Lodash 须要安装插件 babel-plugin-lodash 并更新 Babel 配置文件:react

npm install --save lodash npm install --save-dev babel-plugin-lodash 

更新 Babel 的配置文件 .babelrc:git

{
    "presets": [ "react", "es2015", "stage-0" ], "plugins": [ "lodash" ] } 

使用方式:es6

import _ from 'lodash'; import { add } from 'lodash/fp'; const addOne = add(1); _.map([1, 2, 3], addOne); 

性能

在 Filip Zawada 的文章《How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation》 中提到了 Lodash 提升执行速度的思路,主要有三点:Lazy Evaluation、Pipelining 和 Deferred Execution。下面两张图来自 Filip 的博客:github

lodash-example1-2016-06-26

假设有如上图所示的问题:从若干个球中取出三个面值小于 10 的球。第一步是从全部的球中取出全部面值小于 10 的球,第二步是从上一步的结果取三个球。

lodash-example2-2016-06-26

上图是另外一种解决方案,若是一个球可以经过第一步,那么就继续执行第二步,直至结束而后测试下一个球……当咱们取到三个球以后就中断整个循环。Filip 称这是 Lazy Evaluation Algorithm,就我的理解这并不全面,他后续提到的 Pipelining(管道计算),再加上一个中断循环执行的算法应该更符合这里的图示。

此外,使用 Lodash 的链式调用时,只有显示或隐式调用 .value 方法才会对链式调用的整个操做进行取值,这种不在声明时当即求值,而在使用时求值的方式,是 Lazy Evaluation 最大的特色。

九个实例

受益于 Lodash 的普及程度,使用它能够提升多人开发时阅读代码的效率,减小彼此之间的误解(Loss of Consciousness)。在《Lodash: 10 Javascript Utility Functions That You Should Probably Stop Rewriting》一文中,做者列举了多个经常使用的 Lodash 函数,实例演示了使用 Lodash 的技巧。

1. N 次循环

// 1. Basic for loop. for(var i = 0; i < 5; i++) { // ... } // 2. Using Array's join and split methods Array.apply(null, Array(5)).forEach(function(){ // ... }); // Lodash _.times(5, function(){ // ... }); 

for 语句是执行循环的不二选择,Array.apply 也能够模拟循环,但在上面代码的使用场景下,_.times() 的解决方式更加简洁和易于理解。

2. 深层查找属性值

// Fetch the name of the first pet from each owner var ownerArr = [{ "owner": "Colin", "pets": [{"name":"dog1"}, {"name": "dog2"}] }, { "owner": "John", "pets": [{"name":"dog3"}, {"name": "dog4"}] }]; // Array's map method. ownerArr.map(function(owner){ return owner.pets[0].name; }); // Lodash _.map(ownerArr, 'pets[0].name'); 

_.map 方法是对原生 map 方法的改进,其中使用 pets[0].name 字符串对嵌套数据取值的方式简化了不少冗余的代码,很是相似使用 jQuery 选择 DOM 节点 ul > li > a,对于前端开发者来讲有种久违的亲切感。

3. 个性化数组

// Array's map method. Array.apply(null, Array(6)).map(function(item, index){ return "ball_" + index; }); // Lodash _.times(6, _.uniqueId.bind(null, 'ball_')); // Lodash _.times(6, _.partial(_.uniqueId, 'ball_')); // eg. [ball_0, ball_1, ball_2, ball_3, ball_4, ball_5] 

在上面的代码中,咱们要建立一个初始值不一样、长度为 6 的数组,其中 _.uniqueId 方法用于生成独一无二的标识符(递增的数字,在程序运行期间保持独一无二),_partial 方法是对 bind 的封装。

4. 深拷贝

var objA = { "name": "colin" } // Normal method? Too long. See Stackoverflow for solution: // http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript // Lodash var objB = _.cloneDeep(objA); objB === objA // false 

JavaScript 没有直接提供深拷贝的函数,但咱们能够用其余函数来模拟,好比 JSON.parse(JSON.stringify(objectToClone)),但这种方法要求对象中的属性值不能是函数。Lodash 中的 _.cloneDeep 函数封装了深拷贝的逻辑,用起来更加简洁。

5. 随机数

// Naive utility method function getRandomNumber(min, max){ return Math.floor(Math.random() * (max - min + 1)) + min; } getRandomNumber(15, 20); // Lodash _.random(15, 20); 

Lodash 的随机数生成函数更贴近实际开发,ECMAScript 的随机数生成函数是底层必备的接口,二者都不可或缺。此外,使用 _.random(15, 20, true) 还能够在 15 到 20 之间生成随机的浮点数。

6. 对象扩展

// Adding extend function to Object.prototype Object.prototype.extend = function(obj) { for (var i in obj) { if (obj.hasOwnProperty(i)) { this[i] = obj[i]; } } }; var objA = {"name": "colin", "car": "suzuki"}; var objB = {"name": "james", "age": 17}; objA.extend(objB); objA; // {"name": "james", "age": 17, "car": "suzuki"}; // Lodash _.assign(objA, objB); 

_.assign 是浅拷贝,和 ES6 新增的 Ojbect.assign 函数功能一致(建议优先使用 Object.assign)。

7. 筛选属性

// Naive method: Remove an array of keys from object Object.prototype.remove = function(arr) { var that = this; arr.forEach(function(key){ delete(that[key]); }); }; var objA = {"name": "colin", "car": "suzuki", "age": 17}; objA.remove(['car', 'age']); objA; // {"name": "colin"} // Lodash objA = _.omit(objA, ['car', 'age']); // => {"name": "colin"} objA = _.omit(objA, 'car'); // => {"name": "colin", "age": 17}; objA = _.omit(objA, _.isNumber); // => {"name": "colin"}; 

大多数状况下,Lodash 所提供的辅助函数都会比原生的函数更贴近开发需求。在上面的代码中,开发者可使用数组、字符串以及函数的方式筛选对象的属性,而且最终会返回一个新的对象,中间执行筛选时不会对旧对象产生影响。

// Naive method: Returning a new object with selected properties Object.prototype.pick = function(arr) { var _this = this; var obj = {}; arr.forEach(function(key){ obj[key] = _this[key]; }); return obj; }; var objA = {"name": "colin", "car": "suzuki", "age": 17}; var objB = objA.pick(['car', 'age']); // {"car": "suzuki", "age": 17} // Lodash var objB = _.pick(objA, ['car', 'age']); // {"car": "suzuki", "age": 17} 

_.pick 是 _.omit 的相反操做,用于从其余对象中挑选属性生成新的对象。

8. 随机元素

var luckyDraw = ["Colin", "John", "James", "Lily", "Mary"]; function pickRandomPerson(luckyDraw){ var index = Math.floor(Math.random() * (luckyDraw.length -1)); return luckyDraw[index]; } pickRandomPerson(luckyDraw); // John // Lodash _.sample(luckyDraw); // Colin // Lodash - Getting 2 random item _.sample(luckyDraw, 2); // ['John','Lily'] 

_.sample 支持随机挑选多个元素并返回心的数组。

9. 针对 JSON.parse 的错误处理

// Using try-catch to handle the JSON.parse error function parse(str){ try { return JSON.parse(str); } catch(e) { return false; } } // With Lodash function parseLodash(str){ return _.attempt(JSON.parse.bind(null, str)); } parse('a'); // => false parseLodash('a'); // => Return an error object parse('{"name": "colin"}'); // => Return {"name": "colin"} parseLodash('{"name": "colin"}'); // => Return {"name": "colin"} 

若是你在使用 JSON.parse 时没有预置错误处理,那么它颇有可能会成为一个定时炸弹,咱们不该该默认接收的 JSON 对象都是有效的。try-catch 是最多见的错误处理方式,若是项目中 Lodash,那么可使用 _.attmpt 替代 try-catch 的方式,当解析 JSON 出错时,该方法会返回一个 Error 对象。

随着 ES6 的普及,Lodash 的功能或多或少会被原生功能所替代,因此使用时还须要进一步甄别,建议优先使用原生函数,有关 ES6 替代 Lodash 的部分,请参考文章《10 Lodash Features You Can Replace with ES6》(中文版《10 个可用 ES6 替代的 Lodash 特性》)。

其中有两处很是值得一看:

// 使用箭头函数建立可复用的路径 const object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; [ obj => obj.a[0].b.c, obj => obj.a[1] ].map(path => path(object)); // 使用箭头函数编写链式调用 const pipe = functions => data => { return functions.reduce( (value, func) => func(value), data ); }; const pipeline = pipe([ x => x * 2, x => x / 3, x => x > 5, b => !b ]); pipeline(5); // true pipeline(20); // false 

在 ES6 中,若是一个函数只接收一个形参且函数体是一个 return 语句,就可使用箭头函数简化为:

const func = p => v; // 相似于(不彻底相同) const func = function (p) { return v; } 

当有多重嵌套时,能够简化为:

const func = a => b => c => a + b + c; func(1)(2)(3); // => 6 // 相似于 const func = function (a) { return function (b) { return function (e) { return a + b + c; } } } 
参考资料
相关文章
相关标签/搜索