阅读学习
对柯里化的理解
很多人对于柯里化的理解仅仅停留在“复用参数”上。但我认为函数式编程思想更重要作用的是:解除函数对执行时参数的依赖,增强函数的泛化能力,让函数仅仅包含“纯粹的操作逻辑”,这些操作逻辑要用在什么样的输入上,使用函数时再决定。
使用柯里化的场景:
- 想要实现某个操作逻辑。(举一个最简单的例子,从对象中取出某个属性的值)
- 确认这个操作的输入。(在这个例子中,输入是对象和属性key)
- 将操作的输入作为函数的参数,解除函数实现对【参数的具体值】的依赖。(
function getProp(obj, key)
) - 在函数体中,对这些参数执行一系列操作,实现逻辑。(
function getProp(obj, key) { return obj[key]; }
) -
得到的函数就仅仅封装了“操作逻辑”,函数对于操作的输入不做任何假设,因此函数的泛化能力很强,可以处理任何合法的输入。(在这个例子中,
getProp
可以从任何对象中获取任何属性)“不做任何假设”的说法其实不太准确,比如说
getProp
就假设了obj
参数必须是对象,但这种假设是“完成操作逻辑”的必要要求,“不做任何多余的假设”更准确一些。我在这里使用更绝对的语气,是为了增强自己对这个观点的印象。 - 当用户使用这个函数封装的操作逻辑时,调用这个函数,并且需要在参数中提供操作的输入。函数执行完以后,返回操作的输出。可以将函数看作一个黑盒子,给什么输入就会返回对应的输出,函数本身是“无状态”的。(在这个例子中,
getProp(obj1, 'key1')
和getProp(obj2, 'key2')
,函数能适应任何合法的输入,不管调用多少次,不管传入什么参数,函数的操作逻辑都不会改变) - 通过柯里化,可以在真正执行函数之前先确定某些参数。换句话说,柯里化将输入的函数和参数进行“绑定”,返回绑定后的函数,返回的函数期待剩余的参数。(比如说,我们经常要从Array的原型中获取方法,
let getPropFromArrProto = curry(getProp, Array.prototype);
通过这个新的函数就能直接从Array.prototype
中获取属性)
实现
function curry(fn, ...priorArgs) {
const length = fn.length;
return function judge(...restArgs) {
return priorArgs.length + restArgs.length >= length
? fn.call(this, ...priorArgs, ...restArgs)
: function (...args) { return judge.call(this, ...restArgs, ...args); }
}
}
// 测试代码
var fn = curry(function (a, b, c, d) {
console.log([a, b, c, d]);
return [a, b, c, d];
}, 'p');
fn("a", "b", "c") // [ 'p', 'a', 'b', 'c' ]
fn("a", "b")("c") // [ 'p', 'a', 'b', 'c' ]
fn("a")("b")("c") // [ 'p', 'a', 'b', 'c' ]
fn("a")("b", "c") // [ 'p', 'a', 'b', 'c' ]
其中,
priorArgs.length + restArgs.length >= length
? fn.call(this, ...priorArgs, ...restArgs)
: function (...args) { return judge.call(this, ...restArgs, ...args); }
它的意思是,如果已经接受的参数数量不少于fn
(被柯里化的函数)期待的参数数量,就调用fn并返回结果。
否则,返回一个新的函数,这个函数期待剩余的参数。
调用这个新函数会再次进行参数数量的判断,如果已经接受的参数数量不少于fn
(被柯里化的函数)期待的参数数量,就调用fn并返回结果,否则返回一个新的函数……以此类推。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。