这是我参与8月更文挑战的第3天,活动详情查看: 8月更文挑战
前言
最近在看《JavaScript设计模式与开发实践》这本书,在里边看到了一个反柯里化的概念。反柯里化??以前在学习函数式编程的时候只接触过柯里化,反柯里化还真是不清楚说的啥。
在说反柯里化之前,先来复习下柯里化的基础。
柯里化
函数柯里化在前边函数式编程基础中已经提过了,它主要是把多元函数转化成一元函数。将接受多个参数的函数变换成一个单一参数的函数,并且返回一个接受剩余的参数的函数。主要用来参数复用,延迟计算,提前返回等。
柯里化的实现有很多种,看一个简单的柯里化函数的实现代码:
function curry(fun) {
return function curried() {
let args = Array.prototype.slice.call(arguments); //保存当前输入的所有参数
if(arguments.length < fun.length) { //还在继续输入
return function() {
let innerArgs = Array.prototype.slice.call(arguments);
let allParams = args.concat(innerArgs);
return curried.apply(this, allParams);
}
}else {
return fun.apply(this , args);
}
}
}
上述柯里化的代码主要用来做延迟求值。在参数没输入完的时候它不会去执行fun函数,而是使用闭包将参数缓存下来,同时返回一个新函数。等到函数输入完,再一次性求值。
这里用到了函数的length属性,所以在使用的时候不要用ES6的剩余参数语法,也不要给参数默认值。
反柯里化
那什么是反柯里化呢??是与柯里化相反吗??
书中对反柯里化的作用做了介绍:
反柯里化是为了扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用。
直接看这段文字,好像不太理解具体啥含义,看一下书里给的反柯里化的代码:
Function.prototype.uncurrying = function() {
var self = this; //self为Array.prototype.push
return function() {
//obj = {0:1, length: 1}, arguments = [2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
var obj = Array.prototype.shift.call(arguments);
//Array.ptototype.push(obj, 2)
return self.apply(obj, arguments);
}
}
var testObj = {
length: 1,
0: 1
}
var push = Array.prototype.push.uncurrying();
push(testObj, 2);
console.log(testObj); //{0: 1, 1: 2, length: 2}
从代码可以可以看到,push方法本来是只能用在数组中的,现在通过反柯里化让对象也可以用push方法了。push函数变成了一个通用的函数,不再局限于Array了。
网上针对反柯里化有个通用函数,可以来看一下:
Function.prototype.unCurrying = function() {
let self = this;
return function() {
return Function.prototype.call.apply(self, arguments);
}
}
乍一看,这段代码还是有点难理解,又是call又是apply的。同样拿上边的push函数来测试一下:
-
首先apply函数接收两个参数,第一个是函数体内this对象的指向,第二个是一个数组或类数组的集合。这里的arguments是一个类数组[{0: 1, 1: 2, length: 2}, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ],即[testObj, 2],self为self为Array.prototype.push
-
接下来执行call函数,call函数的第一个参数同样是改变this指向的,第二个参数开始都为传入的的参数。所以这段代码就类似于Array.prototype.push.call(testObj, 2)
现在大概明白了,call和apply主要是为了处理参数和this的问题。
总体来说,反柯里化增加了方法的适用范围,增加了函数的通用性。