前端战五渣学JavaScript——函数柯里化

4,645 阅读5分钟

阅读本篇博客之前需要对JavaScript的闭包有一个很好的了解,可以看一下我之前写的《前端战五渣学JavaScript——闭包》

我自认为大家是在了解闭包的情况下阅读这篇博客的。

最近对什么都提不起兴趣,什么都不想干,什么都不想学,游戏也不想打。。。哎。。但是最近看了一部国产动画电影《哪吒之魔童降世》,感觉现在国产动画的水平真是越来越高了,很不错,值得一看哦

什么是函数柯里化

函数柯里化(function currying),把函数做成咖喱味的??其实我初次听说这个词的时候,感觉好高大上啊,音译,深奥,等我翻看了相关书籍(《JavaScript高级程序设计》)以及一些前辈写的博客以后,发现原来函数柯里化就是使用闭包返回一个函数

柯里化的函数可以延迟接收参数,就是比如我一个函数需要接收的参数是两个,我执行的时候必须接收两个参数,否则我没法执行啊,是不是,就容易出问题。但是柯里化后的函数,可以先接收一个函数,然后再接收一个函数,这么说太生硬了,那我们就来看一个简单的例子(也是全网最普遍的例子)⬇️

这是普通的函数

// 正常我们声明函数的时候是这样的
function 求两数之和(第一个数, 第二个数) {
  return 第一个数 + 第二个数
}
const 和 = 求两数之和(1, 2);
console.log(`两数之和为 ${和}`); // 3

那我们按照柯里化写出来的函数是什么样的呢

// 理解柯里化的思想
function 求两数之和(第一个数) {
  return function (第二个数) {
    return 第一个数 + 第二个数
  }
}
// 第一种方法
const 和 = 求两数之和(1)(2);
console.log(`第一种方法的两个数之和为 ${和}`);
// 第二种方法
const 已接收第一个数的求和函数 = 求两数之和(1);
const 两个数都已接收的和 = 已接收第一个数的求和函数(2);
console.log(`第二种方法的两个数之和为 ${两个数都已接收的和}`);

从上面的函数对比我们就可以发现,柯里化的函数可以先接收一个函数,然后在我们需要的传入第二个参数的时候,我们再传入,并执行最终的结果。

所以我们可以动态生成比如第一个参数相同,第二个参数不同,或者第二个参数相同,第一个参数不同的函数

把函数柯里化的函数

在我们熟知的《JavaScript高级程序设计》中给出了一个方法,这个方法,可以让我们把一个本来不支持柯里化的函数,转化成支持柯里化思想的函数。

function 把函数柯里化(需要柯里化的函数) {
  const 调用柯里化时除了函数以外的参数 = Array.prototype.slice.call(arguments, 1);
  return function () {
    const 后接受的参数 = [...arguments];
    const 最终的参数 = [...调用柯里化时除了函数以外的参数, ...后接受的参数];
    return 需要柯里化的函数.apply(null, 最终的参数);
  }
}

function 求两数之和(第一个数, 第二个数) {
  return 第一个数 + 第二个数
}

const 柯里化的求两数之和 = 把函数柯里化(求两数之和, 1);

console.log(
  `被柯里化后的函数求的两数之和为 ${
    柯里化的求两数之和(2)
  }`
); // 被柯里化后的函数求的两数之和为 3

我们发现,我们可以单独写一个函数,然后把一个普通函数转化成支持柯里化的函数,但是高级程序设计上提供的这个方法,只能提供两次调用,什么意思?我们来看看lodashcurry方法是如何实现的

const _ = require('lodash');

function 求三个数之和(第一个数, 第二个数, 第三个数) {
  return 第一个数 + 第二个数 + 第三个数
}

const 柯里化后的求三个数之和 = _.curry(求三个数之和);

console.log(柯里化后的求三个数之和(1)(2)(3)); // 6
console.log(柯里化后的求三个数之和(1)(2, 3)); // 6
console.log(柯里化后的求三个数之和(1, 2, 3)); // 6

这下我们看到,lodash中对转化柯里化的处理,我们可以任意调用被柯里化的函数,不管参数怎么传,只要传够三个就行,也不管是分几次传。

我们自己来一个柯里化函数吧

我们的需求是什么?

feature

  1. 需要把普通函数转化成柯里函数
  2. 只要参数没达到被转换函数的数量,就返回函数,保存已传参数
  3. 支持参数没达到被转换函数的数量时,无限调用

那我们先来看看我们需要转换的函数⬇️

const 封神榜 = (名字, 宝物, 出生地) => console.log(`我是${出生地}${名字},我有${宝物}`);

封神榜('哪吒', '乾坤圈', '陈塘关'); // 我是陈塘关的哪吒,我有乾坤圈

看看我们普通转化的结果是什么样的⬇️

const 封神榜 = 名字 => 宝物 => 出生地 => console.log(`我是${出生地}${名字},我有${宝物}`);

封神榜('哪吒')('乾坤圈')('陈塘关');

普通转化的函数,我们传参不是很自由,必须穿三次,每次传一个,那我们来个牛逼的⬇️

const 封神榜 = (名字, 宝物, 出生地) => console.log(`我是${出生地}${名字},我有${宝物}`);

const 柯里化 = (需要柯里化的函数, ...参数) => 需要柯里化的函数.length <= 参数.length
  ? 需要柯里化的函数(...参数)
  : (...更多参数) => 柯里化(需要柯里化的函数, ...参数, ...更多参数);

const 柯里化后的封神榜 = 柯里化(封神榜);

柯里化后的封神榜('哪吒', '乾坤圈', '陈塘关'); // 我是陈塘关的哪吒,我有乾坤圈
柯里化后的封神榜('孙悟空')('金箍鲁棒')('花果山'); // 我是花果山的孙悟空,我有金箍鲁棒
柯里化后的封神榜('雷震子', '黄金棍')('终南山玉柱洞'); // 我是终南山玉柱洞的雷震子,我有黄金棍
柯里化后的封神榜()()()()()()()()('姜子牙', '打神鞭', '昆仑山'); // 我是昆仑山的姜子牙,我有打神鞭

看,现在我们实现的柯里化函数已经基本满足我们刚才提的需求了

当然,有柯里化就有反柯里化,但是我觉得没什么必要,我既然都已经柯里化了,柯里化后的函数当然支持非柯里化函数的传参方式,但是有兴趣的小伙伴还是可以了解一下的

总结

其实函数柯里化这个概念,可能我们之前没有听说过,但是工作或者学习中我们可能或多或少接触过这种方式,只是不知道这么写就是柯里化,这个函数就是柯里化函数。

柯里化函数这种方式方法还是很实用的,在日常开发过程中还是会遇到需要这种需求的地方。


我是前端战五渣,一个前端界的小学生。