Lodash用來操作對象和集合,比Underscore擁有更多的功能和更好的性能。
官網:https://lodash.com/
引用:<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
安裝:npm install lodash
首先通過npm安裝lodash:
npm i --save lodash
在js文件中引用lodash:
var _ = require('lodash');
本系列包括:
● lodash用法系列(1),數組集合操作
● lodash用法系列(2),處理對象
● lodash用法系列(3),使用函數
● lodash用法系列(4),使用Map/Reduce轉換
● lodash用法系列(5),鏈式
● lodash用法系列(6),函數種種
■ 使用bind方法顯式綁定this
function say(){ return 'Say ' + this.what; } //使用bind綁定this var sayHello = _.bind(say, {what: 'hello'}); //Say hello console.log(sayHello());
■ 通過bind方式顯式綁定this從而給函數參數變量賦值,或通過函數實參給函數參數變量賦值
//也就是,這里的what變量既可以從this中獲取,也可以從實參中獲取 function sayWhat(what){ //如果what沒有定義 if(_.isUndefined(what)){ what = this.what; } return 'Say ' + what; } //綁定this var sayHello = _.bind(sayWhat, {what: 'hello'}); //不綁定this,直接輸入參數 var saySth = _.bind(sayWhat,{}); //Say hello console.log(sayHello()); //Say haha console.log(saySth('haha'));
■ 通過bind和bindAll來指定對象中字段函數的上下文
/這里的name為某個對象的方法名稱 function bindFunctionName(name){ return _.bind(name,{ first: 'darren', last: 'ji' }) } var obj = { first: 'jack', last: 'chen', name: function(){ return this.first + ' ' + this.last; } }; //給obj的name函數綁定上下文this var nameFunction = bindFunctionName(obj.name); //jack chen console.log(obj.name()); //darren ji console.log(nameFunction()); //讓obj再次成為name函數的上下文 //obj的name函數就不能指定上下文了 _.bindAll(obj); nameFunction = bindFunctionName(obj.name); //jack chen console.log(nameFunction());
以上,bindFunctionName方法內部使用bind方法來改變某個對象字段函數的上下文,然后使用bindAll方法讓obj中的name字段函數的上下文再次變為obj所在的上下文。
■ 給對象中不同的字段函數指定不同的上下文
function getName(){ return this.name; } var obj = { name: 'aa', method1: getName, method2: getName, method3: getName }; //讓obj中method1和method2字段對應的函數上下文為obj所在上下文 _.bindAll(obj, ['method1', 'method2']); var method3 = _.bind(obj.method3,{name: 'bb'}); console.log(obj.method1()); console.log(obj.method2()); console.log(method3());
以上, 通過bindAll方法讓obj的method1和method2對應的字段函數的上下文鎖定在obj所在的上下文,通過bind放讓method3的字段函數的上下文為賦值的上下文。
■ 給對象動態(延遲)添加字段和字段函數
function workLeft(){ return 65 - this.age + ' years'; } var obj = { age: 38 }; //給obj對象綁定一個字段work var work = _.bindKey(obj, 'work'); //給obj的work字段賦值 obj.work = workLeft; //27 years console.log(work());
以上,通過bindKey方法為obj動態、延遲添加了一個work字段,再為work字段賦值,賦給一個函數。
■ 為集合中的每個元素添加字段和字段函數
function workLeft(retirement, period){ return retirement - this.age + ' ' + period; } var collection = [ {age: 34, retirement: 60}, {age: 47}, {age: 28,retirement: 55}, {age:41} ]; var functions = [], result=[]; _.forEach(collection, function(item){ //為集合中的每個元素加上work字段和retirement字段,沒有retirment字段的就加上該字段並附上初值65 //bindKey的返回值是work字段對應的字段函數 functions.push(_.bindKey(item, 'work', item.retirement ? item.retirement : 65)); }); _.forEach(collection, function(item){ //為集合中的每個元素的work字段賦上函數workLeft _.extend(item, {work: workLeft}); }); _.forEach(functions, function(item){ result.push(item('years')); }); //[ '26 years', '18 years', '27 years', '24 years' ] console.log(result);
以上,第一次遍歷集合,給集合延遲綁定上work字段,以及設置retirement的字段值;第二次遍歷集合,使用extend把workLeft函數賦值給work字段;第三次遍歷函數集合,實行每一個函數把結果保存到數組中。
其中extend的用法是把一個對象中的鍵值擴展到另一個對象中去。
var _ = require('lodash'); var obj1 = {foo:23, bar:42}; var obj2 = {bar: 99}; //把obj2的所有字段擴展到obj1上去 //如果obj2的字段obj1已經存在,會重寫obj1上該字段的值 _.extend(obj1, obj2); //{ foo: 23, bar: 99 } console.log(obj1);
■ 給函數不同的實參,函數只有一個形參
function sayWhat(what){ return 'Say ' + what; } var hello=_.partial(sayWhat, 'hello'), goodbye=_.partial(sayWhat, 'goodbye'); //Say hello console.log(hello()); //Say goodbye console.log(goodbye());
■ 給函數不同的實參, 函數有多個形參
function greet(greeting, name){ return greeting + ', ' + name; } var hello = _.partial(greet, 'hello'), goodbye =_.partial(greet, 'goodbye'); //hello, morning console.log(hello('morning')); //hello, morning console.log(goodbye('evening'));
以上,_.partial(greet, 'hello')中的hello實參對應greet函數中的第一個形參greeting。
■ 給Lo-Dash內置函數提供不同的實參
var collection = ['a','b','c']; var random=_.partial(_.random,1,collection.length), sample=_.partial(_.sample,collection); //2 console.log(random()); //a console.log(sample());
■ 把值賦值給某個函數再形成包裹函數
function strong(value){ return '<strong>' + value + '</strong>'; } function regex(exp, val){ exp = _.isRegExp(exp) ? exp : new RegExp(exp); return _.isUndefined(val) ? exp : exp.exec(val); } //提供給strong函數,這個wrapper的值 var boldName =_.wrap('Marianne', strong), //提供給regex這個函數,這個wrapper的形參exp對應的值 getNumber=_.wrap('(\\d+)', regex); //<strong>Marianne</strong> console.log(boldName()); //123 console.log(getNumber('abc123')[1]);
以上,boldName和getNumber方法就像包裹在strong和regext之上的一個函數。
■ 把函數賦值給某個函數再形成包裹函數
//取集合中的一個 var user = _.sample(['aa', 'bb']); var allowed = ['aa', 'dd']; function permission(func) { if (_.contains(allowed, user)) { return func.apply(null, _.slice(arguments, 1)); } throw new Error('denied'); } function eccho(value) { return value; } var welcome = _.wrap(eccho, permission); //are u here 或拋異常 console.log(welcome('are u here'));
■ 限制函數執行的次數,異步場景
var complete= 0, collection= _.range(9999999), progress= _.noop;//return undefined function work(value){ progress(); } function reportProgress(){ console.log(++complete + '%');//記錄進度 //after的第一個形參表示先執行n-1次reportProgress,花費n-1次相當的時間,到n次的時候正真執行reportProgress //本方法也實現了遞歸 progress=complete<100? _.after(0.01*collection, reportProgress) : _.noop; } //先把進度啟動起來 reportProgress(); //遍歷集合的過程就是執行progress方法的過程 //而progress的執行取決於變量complete的值是否小於100 _.forEach(collection, work);
以上,限制了progress函數執行的次數。progress執行的快慢,即控制台顯示百分比的快慢由after函數決定,progress的執行次數由變量complete的值決定。此外,使用after方法又使reportProgress實現了遞歸。
■ 限制函數執行的次數,同步異步混合場景
/這里的回調函數會等到所有的異步操作結束后才運行 function process(arr, callback) { var sync = _.after(arr.length, callback); //這里開始異步 _.forEach(arr, function () { setTimeout(sync, _.random(2000)); }); //這里的同步方法先執行 console.log('timeouts all set'); } process(_.range(5), function () { console.log('callbacks completed'); }); //結果: //timeouts all set //callbacks completed
■ 限制函數執行一次
function getLeader(arr){ return _.first(_.sortBy(arr, 'score').reverse()); } var collection = [ { name: 'Dana', score: 84.4 }, { name: 'Elsa', score: 44.3 }, { name: 'Terrance', score: 55.9 }, { name: 'Derrick', score: 86.1 } ]; //leader函數只執行一次,結果被緩存起來 var leader = _.once(getLeader); //{ name: 'Derrick', score: 86.1 } console.log(leader(collection));
■ 緩存函數
function toCelsius(degrees){ return (degrees - 32) * 5 / 9; } //緩存起來 var celsius =_.memoize(toCelsius); //31.67 C console.log(toCelsius(89).toFixed(2) + ' C'); //31.67 C console.log(celsius(89).toFixed(2) + ' C');
■ 緩存函數,使用緩存函數中的變量值
function toCelsius(degrees) { return (degrees - 32) * 5 / 9; } function toFahrenheit(degrees) { return degrees * 9 / 5 + 32; } //根據indicator,F或C選擇相應的方法 function convertTemp(degrees, indicator){ return indicator.toUpperCase() === 'C' ? toCelsius(degrees).toFixed(2) + ' C' : toFahrenheit(degrees).toFixed(2) + ' F'; } //緩存 var convert = _.memoize(convertTemp, function(degrees, indicator){ return degrees + indicator; }); //192.20 F console.log(convert(89, 'F'));
■ 延遲調用函數,不帶參數
var cnt=-1, max=5, interval=3000, timer; function poll(){ if(++cnt<max){ console.log('polling round ' + (cnt + 1)); timer= _.delay(poll,interval); }else{ clearTimeout(timer); } } //polling round 1 //polling round 2 //polling round 3 //polling round 4 //polling round 5 poll();
■ 延遲調用函數,帶參數
function sayHi(name, delay){ //函數內部定義一個函數 function sayHiImp(name){ console.log('hi '+name); } if(_.isUndefined(delay)){ _.delay(sayHiImp,1,name); } else{ _.delay(sayHiImp, delay, name); } } sayHi('Darren');
■ 所有堆棧被清理后延遲執行某個函數
function cal(){ _.forEach(_.range(Math.pow(2, 25)), _.noop); console.log('done'); } _.defer(cal); //computing... //done console.log('computing...')
■ 通過包裹函數延遲執行某個函數
function deferred(func){ return _.defer.apply(_,([func]).concat(_.slice(arguments,1))); } function setTitle(title){ console.log('Title: "' + title + '"'); } //app為傳入的對象,包含state字段 function setState(app){ console.log('State: "' + app.state + '"'); } //分別包裹下setTitle和setState函數 var title =_.wrap(setTitle, deferred), state=_.wrap(setState, deferred), app={state: 'stopped'}; //Title: "Home" //State: "started" title('Home'); state(app); app.state = 'started';
■ 實現Throttle
Throttle的存在是為了回答"在某段時間內一個函數需要被怎樣執行"這個問題。throttle經常用來更好地控制事件。通常的事件,在一個標准時間內只執行一次,標准時間,一次,這些都是固定的,而throttle可以控制自定義的時間段內執行的次數。
var ele = document.querySelector('#container'); var onMouseMove = _.throttle(function(e){ console.log('x: ' + e.clintX + 'y:' + e.clientY); }, 750); //給元素加上trottle事件 ele.addEventListener('mousemove', onMouseMove); //給窗口加事件 window.addEventListener('haschange', function cleanup(){ ele.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mousemove', cleanup); });
■ 實現Debounce
Debounce的存在也是為了回答"在某段時間內一個函數需要被怎樣執行"這個問題。debounce可以控制自定義時間段內完成一組動作,就好像把多個動作放在了一個單元中。
var size=1500; function log(type, item){ console.log(type + ' ' + item); } var debounced = _.debounce(_.partial(log, 'debounced'),1), throttled=_.throttle(_.partial(log,'throttled'),1); //throttled 0 //throttled 1 //throttled 3 //throttled 858 //debounced 1499 //throttled 1499 _.forEach(_.range(size), debounced); _.forEach(_.range(size), throttled);
■ 合成函數
//面團 function dough(pizza){ if(_.isUndefined(pizza)){ pizza={}; } return _.extend({dough:true},pizza); } //調料 function sauce(pizza){ if(!pizza.dough){ throw new Error('Dough not ready'); } return _.extend({sauce:true}, pizza); } //奶酪 function cheese(pizza){ if(!pizza.sauce){ throw new Error('Sauce not ready'); } return _.extend({cheese:true}, pizza); } var pizza = _.compose(cheese, sauce, dough); //{ cheese: true, sauce: true, dough: true } console.log(pizza()); 如果想控制合成的順序: var pizza = _.flow(dough, sauce, cheese); //{ cheese: true, sauce: true, dough: true } console.log(pizza());
也可以也成這樣:
function makePizza(dough, sauce, cheese){ return { dough: dough, sauce: sauce, cheese: cheese }; } function dough(pizza){ return pizza(true); } function sauceAndCheese(pizza){ return pizza(true, true); } var pizza = _.curry(makePizza); //{ dough: true, sauce: true, cheese: true } console.log(sauceAndCheese(dough(pizza)));
■ 從右到左執行一系列函數
var _ = require('lodash'); function square(n){ return n*n; } var addSuare = _.flowRight(square, _.add); //9 console.log(addSuare(1,2));
■ 從右開始依次輸入形參對應的實參
var greet = function(greeting, name){ return greeting + ' ' + name; } //從右開始依次輸入形參對應的實參 var greetDarren = _.partialRight(greet, 'Darren'); //hi Darren console.log(greetDarren('hi')); //從右開始使用形參的對應的實參占位符 var sayGoodMorning = _.partialRight(greet, 'Good Morning', _); //Good Morning Darren console.log(sayGoodMorning('Darren'));
參考資料:lodash essentials
未完待續~~