有多年開發經驗的工程師,每每都會有本身的一套工具庫,稱爲utils、helpers等等,這套庫一方面是本身的技術積累,另外一方面也是對某項技術的擴展,領先於技術規範的制訂和實現。javascript
Lodash就是這樣的一套工具庫,它內部封裝了諸多對字符串、數組、對象等常見數據類型的處理函數,其中部分是目前ECMAScript還沒有制訂的規範,但同時被業界所承認的輔助函數。天天使用npm安裝Lodash的數量在百萬級以上,這在必定程度上證實了其代碼的健壯性,值得咱們在項目中一試。html
Lodash聽得輔助函數主要分爲如下幾類,函數列表和用法實力請查看Lodash的官方文檔:前端
Array, 適合於數組類型,好比填充數據、查找元素、數組分片等操做java
Collocation, 適用於數組和對象類型,部分適用於字符串,好比分組、查找、過濾等操做react
Function, 適用於函數類型,好比節流、延遲、緩存、設置鉤子等操做es6
Lang, 廣泛適用於各類類型,經常使用於執行類型判斷和類型轉換算法
Math, 使用與數值類型,經常使用於執行數學運算npm
Number, 適用於生成隨機數,比較數值與數值區間的關係編程
Object, 適用於對象類型,經常使用於對象的建立、擴展、類型轉換、檢索、集合等操做數組
Seq, 經常使用於建立鏈式調用,提升執行性能(惰性計算)
String, 適用於字符串類型
lodash/fp 模塊提供了更接近函數式編程的開發方法,其內部的函數通過包裝,具備immutable、auto-curried、iteratee-first、data-last(官方介紹)等特色。
Fixed Arity,固化參數個數,便於柯里化
Rearragned Arguments, 從新調整參數位置,便於函數之間的聚合
Capped Iteratee Argument, 封裝Iteratee參數
在React + Webpack + Babel(ES6)的開發環境中,使用Lodash須要安裝插件babel-plugin-lodash並更新Babel配置文件:
npm install --save lodash npm install --save-dev babel-plugin-lodash
更新Bable的配置文件 .babelrc:
{ "presets":[ "react", "es2015", "stage-0" ], "plugins":[ "lodash" ] }
使用方式:
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的博客:
假設有如上圖所示的問題: 從若干個求中取出三個面值小於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也能夠模擬循環,但在上面代碼的使用場景下,_.tiems()的解決方法更加簡潔和易於理解。
// 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_6]
在上面的代碼中,咱們要建立一個初始值不一樣、長度爲6的數組,其中 _.uniqueId 方法用於生成獨一無二的標示符(遞增的數字,在程序運行期間保持獨一無二), _.partial 方法是對 bind 的封裝。
var objA = { "name": "colin" } // 經常使用的方法通常會比較長,循環對象等 // 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 函數封裝了深拷貝的邏輯,用起來更加簡潔。
// Native 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, ojbB);
_.assign 是淺拷貝, 和ES6新增的 Object.assign 函數功能一致(建議優先使用Object.assign)。
// Native method: Remove an array of keys from object Object.prototype.remove = function(arr) { var that = this; arr.forEach(function(key){ delete(this[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", "car": "suzuki"};
大多數狀況下,Lodash所提供的輔助函數都會比原聲的函數更貼近開發需求。在上面的代碼中,開發者可使用數組、字符串以及函數的方式篩選對象的屬性,而且最終會返回一個新的對象,中間執行篩選時不會對舊對象產生影響。
// Native method: Returning a new object with selected properties Object.prototype.pick = function(arr) { var _this = this; var obj = {}; arr.forEach(function(){ 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 luckDraw = ["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 個可用 ES6 替代的 Lodash 特性》。
其中有兩處分廠值得一看:
// 使用箭頭函數建立可複用的路徑 const object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; [ obj => obj.a[0].b.c, obj => ojb.a[1] ].map(path => path(object)); // 使用箭頭函數編寫鏈式調用 const pipe = function => 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 (c) { return a + b + c; } } }