12博
  咨询电话:15239178384

12bet游戏开户

柯里化与反柯里化

前言

柯里化,可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数。因为这样的特性,也被称为部分计算函数。柯里化,是一个逐步接收参数的过程。在接下来的剖析中,你会深刻体会到这一点。

反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。目的是创建一个更普适性的函数,可以被不同的对象使用。有鸠占鹊巢的效果。

一、柯里化

1.1 例子

实现 add(1)3)(4)() = 10 的效果

依题意,有两个关键点要注意:

传入参数时,代码不执行输出结果,而是先记忆起来当传入空的参数时,代表可以进行真正的运算

完整代码如下:

function currying(fn){ var allArgs = []; return function next(){ var args = [].slice.call(arguments); if(args.length > 0){ allArgs = allArgs.concat(args); return next; }else{ return fn.apply(null, allArgs); } } }var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum;});

1.2 记忆传入参数

由于是延迟计算结果,所以要对参数进行记忆。这里的实现方式是采用闭包。

function currying(fn){ var allArgs = []; return function next(){ var args = [].slice.call(arguments); if(args.length > 0){ allArgs = allArgs.concat(args); return next; } } }

当执行var add = currying(...)时,add变量已经指向了next方法。此时,allArgsnext方法内部有引用到,所以不能被GC回收。也就是说,allArgs在该赋值语句执行后,一直存在,形成了闭包。依靠这个特性,只要把接收的参数,不断放入allArgs变量进行存储即可。所以,当arguments.length > 0时,就可以将接收的新参数,放到allArgs中。最后返回next函数指针,形成链式调用。

1.3 判断触发函数执行条件

题意是,空参数时,输出结果。所以,只要判断arguments.length == 0即可执行。另外,由于计算结果的方法,是作为参数传入currying函数,所以要利用apply进行执行。综合上述思考,就可以得到以下完整的柯里化函数。

function currying(fn){ var allArgs = []; // 用来接收参数 return function next(){ var args = [].slice.call(arguments); // 判断是否执行计算 if(args.length > 0){ allArgs = allArgs.concat(args); // 收集传入的参数,进行缓存 return next; }else{ return fn.apply(null, allArgs); // 符合执行条件,执行计算 } } }

1.4 总结

柯里化,在这个例子中可以看出很明显的行为规范:

逐步接收参数,并缓存供后期计算使用不立即计算,延后执行符合计算的条件,将缓存的参数,统一传递给执行方法

1.5 扩展

实现 add(1)3)(4)(5) = 15 的效果。很多人这里就犯嘀咕了:我怎么知道执行的时机?其实,这里有个忍者技艺:valueOftoStringjs在获取当前变量值的时候,会根据语境,隐式调用valueOftoString方法进行获取需要的值。那么,实现起来就很简单了。

function currying(fn){ var allArgs = []; function next(){ var args = [].slice.call(arguments); allArgs = allArgs.concat(args); return next; } // 字符类型 next.toString = function(){ return fn.apply(null, allArgs); }; // 数值类型 next.valueOf = function(){ return fn.apply(null, allArgs); } return next;}var add = currying(function(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum;});

二、反柯里化

2.1 例子

有以下轻提示类。现在想要单独使用其show方法,输出新对象obj中的内容。

// 轻提示function Toast(option){ this.prompt = "";}Toast.prototype = { constructor: Toast, // 输出提示 show: function(){ console.log(this.prompt); }};// 新对象var obj = { prompt: "新对象"};

用反柯里化的方式,可以这么做

function unCurrying(fn){ return function(){ var args = [].slice.call(arguments); return fn.apply(args[0], args.shift()); }}var objShow = unCurrying(Toast.prototype.show);objShow(obj); // 输出"新对象"

2.2 反柯里化的行为

非我之物,为我所用增加被反柯里化方法接收的参数

在上面的例子中,Toast.prototype.show方法,本来是Toast类的私有方法。跟新对象obj没有半毛钱关系。经过反柯里化后,却可以为obj对象所用。为什么能被obj所用,是因为内部将Toast.prototype.show的上下文重新定义为obj。也就是用apply改变了this指向。而实现这一步骤的过程,就需要增加反柯里化后的objShow方法参数。

2.3 另一种反柯里化的实现

Function.prototype.unCurrying = function(){ var self = this; return function(){ return Function.prototype.call.apply(self, arguments); }}// 使用var objShow = Toast.prototype.show.unCurrying();objShow(obj);

这里的难点,在于理解Function.prototype.call.apply(self, arguments);可以分拆为两步:

1) Function.prototype.call.apply(...)的解析

可以看成是callFunction.apply(...)。这样,就清晰很多。callFunctionthis指针,被apply修改为self然后执行callFunction -> callFunction(arguments)

2) callFunction(arguments)的解析

call方法,第一个参数,是用来指定this的。所以callFunction(arguments) -> callFunction(arguments[0], arguments[1-n])由此可以得出,反柯里化后,第一个参数,是用来指定this指向的。

3)为什么要用apply(self, arguments)如果使用apply(null, arguments),因为null对象没有call方法,会报错。

三、实战

3.1 判断变量类型

var fn = function(){};var val = 1;if(Object.prototype.toString.call(fn) == "[object Function]"){ console.log(`${fn} is function.`);}if(Object.prototype.toString.call(val) == "[object Number]"){ console.log(`${val} is number.`);}

上述代码,用反柯里化,可以这么写:

var fn = function(){};var val = 1;var toString = Object.prototype.toString.unCurrying();if(toString(fn) == "[object Function]"){ console.log(`${fn} is function.`);}if(toString(val) == "[object Number]"){ console.log(`${val} is number.`);}

后记

其实,反柯里化和泛型方法一样,只是理念上有一些不同而已。理解这种思维即可。

, 1, 0, 9);