[js基础]柯里化究竟为何物?

简介

柯里化,英语:Currying。是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

  • 是一个部分配置多参数函数的过程
  • 每一步都返回一个接受单个参数的部分配置好的函数。

可以理解成,将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用,例如f(1)(2)。其数学表达式为:

z = f(x, y) 转换成 z = f(x)(y) 的形式
// 普通的add函数
function add(x, y{
    return x + y
}

// Currying后
function curryingAdd(x{
    return function (y{
        return x + y
    }
}

add(1,2)           // 3
curryingAdd(1)(2)   // 3

如果用链式调用,可以这么写

// 链式调用
var d = 1;
d.add(2).add(3).add(4//输出10

Number.prototype.add = function(x){
 return this + x;
}

这样看来,柯里化和链式调用是有异曲同工之妙的。

用es6的箭头函数语法来理解,可能更为直观一些。

let add = x => y => x + y
let add2 = add(2)

具体调用过程如下:

前 n – 1 次调用,其实是为了将参数传递进去,并没有调用最内层函数体,最后一次调用才会调用最内层函数体,并返回最内层函数体的返回值。

所以连续箭头函数可以看做是多次柯里化函数的 es6 写法。

n 个连续箭头组成的函数实际上就是柯里化了 n – 1次,其中涉及到一些作用域链的内容,可以到参考资料中查阅了解一下或者自行搜索。

let test = a => b => c => {console.log();}
a()()()

它突出了一种思想——降低适用范围,提高适用性




题目

实现 add(1)(2)(3)

(一)粗暴版

function add (a) {
 return function (b) {
  return function (c) {
      return a + b + c;
  }
 }
}
console.log(add(1)(2)(3)); // 6

(二)柯里化解决方案

  • 参数长度固定
const curry = (fn) => (
    judge = (...args) => args.length === fn.length  ? 
    fn(...args): 
    (...arg) => judge(...args, ...arg)
);

const add = (a, b, c) => a + b + c;
const curryAdd = curry(add);
console.log(curryAdd(1)(2)(3)); // 6
console.log(curryAdd(12)(3)); // 6
console.log(curryAdd(1)(23)); // 6

实现add(1, 2, 3)(4)(5)(6)

  • 参数长度不固定
function add (...args{
    //求和
    return args.reduce((a, b) => a + b)
}

function currying (fn{
    let args = []
    return function temp(...newArgs{
        if (newArgs.length) {
            args = [
                ...args,
                ...newArgs
            ]
            return temp
        } else {
            let val = fn.apply(this, args)
            
            //保证再次调用时清空
            args = [] 
            
            return val
        }
    }
}

let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(45)(5)())  //20
console.log(addCurry(1)(2)(345)())  //15
console.log(addCurry(1)(2345)())  //15

另一种写法

function add({
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function({
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function ({
        return _args.reduce(function (a, b{
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(123)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(26)(1)                // 9

应用




lodash.curry

_.curry(func, [arity=func.length])

创建一个函数,该函数接收 func 的参数,要么调用func返回的结果,如果 func 所需参数已经提供,则直接返回 func 所执行的结果。或返回一个函数,接受余下的func 参数的函数,可以使用 func.length 强制需要累积的参数个数。

_.curry.placeholder值,默认是以 _ 作为附加部分参数的占位符。

参数

  1. func (Function): 用来柯里化(curry)的函数。
  2. [arity=func.length] (number): 需要提供给 func 的参数数量。

返回

(Function): 返回新的柯里化(curry)函数。

例子

var abc = function(a, b, c{
  return [a, b, c];
};
 
var curried = _.curry(abc);
 
curried(1)(2)(3);
// => [1, 2, 3]
 
curried(12)(3);
// => [1, 2, 3]
 
curried(123);
// => [1, 2, 3]
 
// Curried with placeholders.
curried(1)(_, 3)(2);
// => [1, 2, 3]




减少重复传递不变的部分参数

function simpleURL(protocol, domain, path{
    return protocol + "://" + domain + "/" + path;
}

上面这个函数是将三个参数生成一个完成的url.调用如下:

let myurl = simpleURL('http''mysite''home.html');
let myurl2 = simpleURL('http''mysite''aboutme.html');

然后你会发现,前两个参数保持不变,但每次调用都需要传递。所以可以对其优化,仅传递最后一个变化的参数。

function simpleURL(path) {
   return "http://mysite/" + path;
}

这种就属于硬写了,是绝对不允许的。

使用lodash.curry库函数使函数柯里化

// 避免每次调用重复传参
let myURL1 = _.curry(simpleURL)('https''mysite');
let res1 = myURL1('home.html');    //

console.log(res1);//https://mysite/home.html

let myURL2 = _.curry(simpleURL)('http''mysite');
let res2 = myURL2('aboutme.html');    //

console.log(res2);//http://mysite/aboutme.html




将柯里化后的callback参数传递给map, filter等函数。

比如我们有这样一段数据:

let persons = [{name'kevin'age11}, {name'daisy'age24}]

如果我们要获取所有的 name 值,我们一般会这样实现:

let names = persons.map(function (item{
    return item.name;
});

可以利用柯里化改写成如下:

let getProp = _.curry(function (key, obj) {
    return obj[key]
});
let names = persons.map(getProp('name'))

getProp 函数编写一次后可以多次使用。

let persons = [{name: 'kevin', age: 11}, {name: 'daisy', age: 24}]

let getProp = _.curry(function (key, obj) {
    return obj[key]
});
let names2 = persons.map(getProp('name'))
console.log(names2); //['kevin''daisy']

let ages2 = persons.map(getProp('age'))
console.log(ages2); //[11,24]

在这个场景中,将callback柯里化之后,就能实现callback的复用了,而且非常灵活,这样不需要每次map计算都新写一个匿名函数,并在回调里加上特有的逻辑,导致其无法重用。




固定部分配置项

思路和减少重复传递不变的部分参数大同小异。

例如,有一个函数加载配置,前面几项固定了,而后面部分项不固定

// 举个例子 一个爬虫 
let load_config_spider = spider.load("target.com""get""id=1""name=xxx")
load_config_spider("sex=m")("page=15")




参考资料

[详解JS函数柯里化]https://www.jianshu.com/p/2975c25e4d71

[JS 执行环境、作用域链、活动对象]https://segmentfault.com/a/1190000015782315

《[简述几个非常有用的柯里化函数使用场景]https://segmentfault.com/a/1190000015281061)

[《lodash源码学习curry,curryRight》]https://www.cnblogs.com/wandiao/p/7188588.html


原文始发于微信公众号(豆子前端):[js基础]柯里化究竟为何物?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56771.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!