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

 

未完待续~~

 

posted @ 2015-12-01 21:41  Darren Ji  阅读(15416)  评论(0编辑  收藏  举报

我的公众号:新语新世界,欢迎关注。