js高阶函数应用—函数柯里化和反柯里化(二)

上一篇文章中咱们介绍了函数柯里化,顺带提到了偏函数,接下来咱们继续话题,进入今天的主题—函数的反柯里化。java

在上一篇文章中柯里化函数你可能须要去敲许多代码,理解不少代码逻辑,不过这一节咱们讨论的反科里化你可能不须要看不少代码逻辑,主要是理解反柯里化的核心思想,其实这种思想可能在你刚入门js时候就接触到了,并且你几乎每天在写代码过程当中使用它。编程

首先须要理解反柯里化咱们先来回顾上一节的内容,科里化简单归纳一下就是咱们作了这样一件事情:把接受多个参数的函数变换成接受一个单一参数的函数,而且返回新的函数来执行余下的数,公式表示基本模式为fn(a,b,c,d)=>fn(a)(b)(c)(d)。数组

那么很容易类比过来,反柯里化就是fn(a)(b)(c)(d)=>fn(a,b,c,d)是这样吗?其实就是这样,不要怀疑就是这么简单,只不过实现过程没有这么直接明了而已,反柯里化大概是作了这么一件事情:把已经内置的特定使用场景的函数经过参数解放出来,提升函数的适用范围。数据结构

转化为公式:app

curyyA=fn(a);编程语言

curryA(b)(c)(d)=>fn(a,b,c,d);函数

或者ui

curyyAb=fn(a)(b);//或者curyyAb=fn(a,b);this

curryAb(c)(d)=>fn(a,b,c,d);spa

......以此类推

为了方便理解咱们把上一节中的curry函数add版本拿过来

const curry = (fn, ...arg) => {
    let all = arg || [],
        length = fn.length;
    return (...rest) => {
        let _args = all.slice(0);
        _args.push(...rest);
        if (_args.length < length) {
            return curry.call(this, fn, ..._args);
        } else {
            return fn.apply(this, _args);
        }
    }
}
const add = curry((a, b) => a + b);
const add6 = add(6);
console.log(add6(1)); //7
console.log(add6(2)); //8

能够看到咱们柯里化后的函数,每次执行了后返回的函数对于应用场景针对性越强,例如这个add6就是任意一个数和6的和,比起原来的add能够实现任意两数的和,add6应用范围更窄了,不过add6这个应用场景更有针对性,例如咱们就须要一个任意数与6的和的时候这个add6就适应咱们的场景。

这样应该很容易理解了吧,若是还有问题咱们来看一个更简单的例子:

let getTag = (type) => {
    return `小明是一个${type}`
}
getTag('好学生');//小明是一个好学生
getTag('好老师');//小明是一个好老师

这个getTag函数根据传入的参数返回小明的类型“小明是一个xxx”,不过这个函数是咱们在专门获得小明的类型用的,若是咱们如今有个需求须要的到“小华是一个xxx”,你总不会再写一个函数

 getTagHua = (type) => `小华是一个${type}`

来获得“小华是一个xxx”的结果吧,就算刚入门编程语言时候咱们也不会这么写;要实现这个需求很简单,再传入一个参数就好了

let getTag2 = (name, type) => `${name}是一个${type}`

getTag2('小华', '好学生')//小华是个好学生

到这里咱们能够对比一下getTag和getTag2这两个函数,咱们发现:

1.getTag是一个getTag2柯里化后带着一个“小明”的内置参数的版本

2.getTag只针对获得小明类型的场景使用

3.getTag2是一个getTag泛化后的版本,适用范围更广

能够理解为getTag2是getTag的反柯里化函数,是否是很简单,反柯里化的函数编程思想咱们每天在用,只是没注意到而已。如今再回去看以前的公式

curyyA=fn(a);

curryA(b)(c)(d)=>fn(a,b,c,d);

这种类型的转化是否是就很容易理解了。

接下来咱们接续咱们的话题,咱们如今知道了,反柯里化是一种编程思想,经过解析出函数内部限定条件,而后把限定条件当作参数传给函数,从而提升函数使用范围的一种编程思想,既然这样咱们就很容易理解咱们下面这种状况了

class Book {
    constructor(name) {
        this.name = name;
    }
    static declare() {
        console.log('A Study Method');
    }
    sayName() {
        console.log(this.name);
    }
}

class Blog {
    constructor(name) {
        this.name = name;
    }
}
let book = new Book('javacript语言精粹');
let blog = new Blog('博客园');

book.sayName();                     //javacript语言精粹  
Book.prototype.sayName.call(blog); //博客园
Book.declare.call(blog);           //A Study Method

如上面咱们在开发常常遇到的,blog想用book类的方法,用call和apply改变一下this指向就好了,咱们经常使用的call,apply函数自己就是反科里化思想的体现,把函数的调用对象当作参数传给函数,从而提升函数的适用范围,相似于作了这么一件事情:

obj.fn(a,b)=>fn(object,a,b) 的转化

经过转化使得fn再也不只适用于obj调用还可让其余的object调用,提升其适用范围

那么咱们把这个转化过程用函数实现一下

Function.prototype.uncurrying = function() { //外边这个不要写成箭头函数,由于咱们具体反柯里化什么函数是咱们调用时候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

let sayName = Book.prototype.sayName.uncurrying();
let deClare = Book.declare.uncurrying();
sayName(blog) //博客园
deClare(blog) //A Study Method
deClare(book) //A Study Method

能够看到咱们把Book类才能调用的静态方法declare,和book实例的sayName反柯里化后,这种只针对类名调用的方法和只针对Book类对象调用的方法可让其余对象调用了。

固然咱们也能够把一些js内置对象的方法uncurrying一下,好比一个字符串‘sdjkfjksfsdkslkdjsdf’,咱们想把它每个字符都拆出来放到一个数组中,或者每一项都拼个固定的字符再返回一数组,咱们能够吧Array里的map方法uncurrying一下:

let map = Array.prototype.map.uncurrying();
console.log(map('yuweryiweryuie', val => val + 'test'))

固然不少js内置对象的方法能够uncurrying的,这里不作过多介绍,由于都是咱们经常使用的call,apply的场景,即一些具备相似数据结构或者相同迭代器的对象咱们一般会借用其余的对象方法

咱们这里还给出一种上述uncurrying的实现方式:

Function.prototype.uncurrying = function() {
    return (...rest) => Function.prototype.call.apply(this, rest);
}

若是你不是很理解代码也不要紧,没太大影响,由于这个实现只是把以前的

Function.prototype.uncurrying = function() { //外边这个不要写成箭头函数,由于咱们具体反柯里化什么函数是咱们调用时候才知道的
    return (obj, ...rest) => this.apply(obj, rest);
}

这个函数的传obj的这个工做交给call来完成了,若是你仍是不理解建议去mdn上看看call和apply用法,理一下逻辑就行,这里不作过多阐释,好了咱们currying和uncurrying的内容就到这了。

相关文章
相关标签/搜索