仰望星空的天台

0%

函数柯里化

函数柯里化

一个来自百度实习一面的题目,虽然以前听过这个名词,但是并没有仔细的去研究它,后来翻了翻 javascript高级程序设计(P604) 才知道大概了解这玩意

什么事柯里化呢? 引一下度娘百科

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

很复杂,我也是一脸懵逼的给你们复制下来的,我所理解的函数柯里化,应该是一种js的实现技巧

所以我们还是先来关心一下柯里化函数能够实现怎样的功能,再来细究其中的原理

功能: 参数复用

栗子:


funciton print( a , b , c ) {
	console.log( a + "喜欢吃" + b + " 和 " + c );
}

print("Owen" , "鸡排" , "牛排");
print("Owen" , "草莓" , "苹果");

上面的代码主要是想一次打印一个人喜欢吃的两种食物

如果我们将print函数进行柯里化之后就能简化一下


funciton print( a , b , c ) {
	console.log( a + "喜欢吃" + b + " 和 " + c );
}

var Owen =  curring(print, "Owen") // 先假设有这么个黑箱子

Owen( "鸡排" , "牛排" );
Owen( "草莓" , "苹果" ); // 实现的参数的复用

看到了curring函数的作用了吧?可以将一个或多个的参数固定,而变化的参数在再调用的时候进行赋值

看到这里大家应该有点兴趣了(没兴趣的出门左拐厕所一边蹲去)

我就开始丢一下书上的代码啦:

var currying = function (fn) {
	var args = Array.prototype.slice.call(arguments , 1); // 获取固定参数时的除fn 以外的所有参数(我们可以认为是默认参数,参数的类型可以是函数,对象,数字,字符 Json等等)
	
	return function () {
		// 返回的函数再进行调用的时候,获取全部参数(变化参数)
		// 将default默认的参数和变化参数合并
		var fArgs = args.concat( Array.prototype.slice.call( arguments ) );
	
		// 执行被柯里化的函数,并将合并好的参数丢给该函数
		fn.apply( null , fArgs );
	}
}

看不懂?(看不懂的也出门左拐厕所哈哈)

除了上面的使用方法外,我又想到了另外种curry化的变式

function love ( a , b ) {
	console.info( a.name + " love " + b.name );
}

function hate ( a , b ) {
	console.info( a.name + " hate " + b.name );
} 

var c = currying(function ( Default , nextAdd , next ) {

	next( Default , nextAdd );

}, {
	"name" : "Owen",
});

c(
	{
		"name" : "Zyz",
	},love
); 

c(
	{
		"name" : "Luffy",
		
	},love
)

c(
	{
		"name" : "Tom",
	},hate
);

// Owen love Zyz
// Owen love Luffy
// Owen hate Tom

这样我就可以很好的自由搭配固定的参数 , 变化的参数 , 需要执行那个函数了

反柯里化 unCurrying

javascript 高级编程上应该是没有反柯里化的内容的,上网一搜,发现了挺多关于反柯里化的博文的

就让我们来看看什么是反柯里化咯

鸭子辩型

谈论unCurrying前先来段小故事

很久以前有个皇帝喜欢听鸭子呱呱叫,于是他召集大臣组建一个一千只鸭子的合唱团。大臣把全国的鸭子都抓来了,最后始终还差一只。有天终于来了一只自告奋勇的鸡,这只鸡说它也会呱呱叫,好吧在这个故事的设定里,它确实会呱呱叫。 后来故事的发展很明显,这只鸡混到了鸭子的合唱团中。— 皇帝只是想听呱呱叫,他才不在乎你是鸭子还是鸡呢。

是的,无论是鸭是鸡,能用就行

再来看个栗子吧 比如我们现在需要获取一组dom 结点

<div class="aaa"></div>
<div class="aaa"></div>
<div class="aaa"></div>

获取到后,在每个里面插入一个span,并且返回每个div下span的集合

基础好点的同学们应该立马就想到 使用Array下 的map方法进行映射返回一组数组 (还不知道map方法的同学可以参考一下我的另外一篇博文

但是,遗憾的是使用DOM找到的集合并不是Array类型的,而是HTMLCollection 类型的,而HTMLCollection并没有Map方法,


var oDivs = document.getElementsByClassName("aaa");
console.log(Object.prototype.toString.call( oDivs )) //[object HTMLCollection]

怎么办呢?

还记得上面的故事吗? 无论是鸡还是鸭,能叫就行 换成这里的法则就是 无论是在Array 下的方法 还是在Html Collection 下的方法 , 只要就行

什么意思呢?意思就是Js 中的原生方法,并不会验证使用的对象是不是相应的对象

也就是说,例如 Array类 下的方法,并不会检查使用这个方法的对象 到底是不是Array类型的实例 只要传参正确,可以运行就不会报错

这样我们就可以借Array下的Map方法 给HTMLCollection 类型使用

具体实现很简单


var oDivs = document.getElementsByClassName("aaa");
	
var oSpans = Array.prototype.map.call(oDivs, function (value , idx , array) {
	var oSpan = document.createElement("span");
	value.appendChild( oSpan );
	return oSpan;
});

console.log( oSpans );

虽然实现、原理都很简单的,但是各位不觉得很麻烦吗? 每次使用map的时候 都需要写一遍Array.prototype.map.call 有的人也许为了简写 临时实例化一个数组对象 ` [].map.call `,但是依然还是挺麻烦的

有什么方法可以把一些方法简单的固定下来,不需要总是使用call来延伸作用域呢?

这就引出我们今天讨论的 函数反柯里化

函数反柯里化的具体实现

Function.prototype.uncurrying = function () {
	var $self = this;
	return function () {
		return Function.prototype.call.apply( $self , arguments );
	}
}

看起来很简单,但是挺复杂的(什么鬼call.apply 真的是第一次见着玩意)

所以我为此画了一张图片,来帮助大家一起理解这个函数

uncurrying

使用这个uncurrying 我们就可以对任何对象下的任何函数,进行借用了

例如像刚刚那个例子


Function.prototype.uncurrying = function () {
	var $self = this;
	return function () {
		return Function.prototype.call.apply( $self , arguments );
	}
}


var oDivs = document.getElementsByClassName("aaa");	

var Map = Array.prototype.map.uncurrying();
var oSpans = Map( oDivs ,  function ( value , idx , array ) {
	var oSpan = document.createElement("span");
	value.appendChild( oSpan );
	return oSpan;
})

console.log( oSpans );

还有push方法

Function.prototype.uncurrying = function () {
	var $self = this;
	return function () {
		return Function.prototype.call.apply( $self , arguments );
	}
}

var push = Array.prototype.uncurrying();

var a = {};  
push( a , ["Owen" , "Luffy" , "Zyz"] );

console.log(a); // Object {0: "Owen", 1: "Luffy", 2: "Zyz", length: 3}

最后引一段AlloyTeam 的大神曾探写的代码,这段代码根据你自己的需要为自定义构造函数,复制所需要的对象下的所有方法

Function.prototype.uncurrying = function () {
	var $self = this;
	return function () {
		return Function.prototype.call.apply( $self , arguments );
	}
}

var add_fn = function ( obj , targetObj , keys ) {
	for( var i = 0 , arr = keys.split(",") , fn; fn = arr[i++] ; ) {
		// 获取需求,取出每一个需求的函数名称

		(function ( fn ) {
			var newFn = targetObj.prototype[ fn ].uncurrying(); // 从目标对象中获取需要的方法,并进行柯里化
			obj[ fn ] = function () {

				// 当调用这个函数某个函数的时候,将借用的newFn 函数执行,并将this 对象改为调用者,还有将调用者传的所有参数,一股脑儿的全塞进去
				newFn.apply( this , [ this ].concat( Array.prototype.slice.call( arguments ) ) );
				return this;
			}	
		})( fn );
	}
}




// 构造自己的工厂
function Owen() {

}

add_fn( Owen.prototype , Array , "push,indexOf,forEach" );
add_fn( Owen.prototype , Object , "toString" );


var myBaby = new Owen();

myBaby.push(1).push(3).forEach( function ( value , idx ) {
	console.log( value , idx );
});

console.log( myBaby.toString() ) // Owen{ }

还有一点就是切记不要滥用反柯里技术,例如一个对象,你给他String下的split方法它肯定是无法使用的。

感谢

函数柯里化

Javascript中有趣的反柯里化技术

Table of Contents