原文額地址 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
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
假設有如上圖所示的問題:從若干個球中取出三個面值小於 10 的球。第一步是從全部的球中取出全部面值小於 10 的球,第二步是從上一步的結果取三個球。
上圖是另外一種解決方案,若是一個球可以經過第一步,那麼就繼續執行第二步,直至結束而後測試下一個球……當咱們取到三個球以後就中斷整個循環。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. 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()
的解決方式更加簡潔和易於理解。
// 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
,對於前端開發者來講有種久違的親切感。
// 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
的封裝。
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
函數封裝了深拷貝的邏輯,用起來更加簡潔。
// 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 之間生成隨機的浮點數。
// 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
)。
// 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
的相反操做,用於從其餘對象中挑選屬性生成新的對象。
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
支持隨機挑選多個元素並返回心的數組。
// 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; } } }