lodash用法系列(3),使用函数
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
未完待续~~