浅谈JS中的反柯里化( uncurrying)


Posted in Javascript onAugust 17, 2017

反柯里化

相反,反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用.

即把如下给定的函数签名,

obj.func(arg1, arg2)

转化成一个函数形式,签名如下:

func(obj, arg1, arg2)

这就是 反柯里化的形式化描述。

例如,下面的一个简单实现:

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

function sayHi () {
  return "Hello " + this.value +" "+[].slice.call(arguments);
}
var sayHiuncurrying=sayHi.uncurrying();
console.log(sayHiuncurrying({value:'world'},"hahaha"));

解释:

  • uncurrying是定义在Function的prototype上的方法,因此对所有的函数都可以使用此方法。调用时候:sayHiuncurrying=sayHi.uncurrying(),所以uncurrying中的 this 指向的是 sayHi 函数; (一般原型方法中的 this 不是指向原型对象prototype,而是指向调用对象,在这里调用对象是另一个函数,在javascript中函数也是对象)
  • call.apply(that, arguments) 把 that 设置为 call 方法的上下文,然后将 arguments 传给 call方法,前文的例子,that 实际指向 sayHi,所以调用 sayHiuncurrying(arg1, arg2, ...) 相当于 sayHi.call(arg1, arg2, ...);
  • sayHi.call(arg1, arg2, ...), call 函数把 arg1 当做 sayHi的上下文,然后把 arg2,... 等剩下的参数传给sayHi,因此最后相当于 arg1.sayHi(arg2,...);
  • 因此,这相当于 sayHiuncurrying(obj,args) 等于 obj.sayHi(args)。

最后,我们反过来看,其实反柯里化相当于把原来 sayHi(args) 的形式,转换成了 sayHiuncurrying(obj,args),使得sayHi的使用范围泛化了。 更抽象地表达, uncurryinging反柯里化,使得原来 x.y(z) 调用,可以转成 y(x',z) 形式的调用 。 假设x' 为x或者其他对象,这就扩大了函数的使用范围。

通用反柯里化函数

上面例子中把uncurrying写进了prototype,这不太好,我们其实可以把 uncurrying 单独封装成一个函数;

var uncurrying= function (fn) {
  return function () {
    var args=[].slice.call(arguments,1);
    return fn.apply(arguments[0],args);    
  }  
};

上面这个函数很清晰直接。

使用时 调用 uncurrying 并传入一个现有函数 fn, 反柯里化函数会返回一个新函数,该新函数接受的第一个实参将绑定为 fn 中 this的上下文,其他参数将传递给 fn 作为参数。

所以,对反柯里化更通俗的解释可以是 函数的借用,是函数能够接受处理其他对象,通过借用泛化、扩大了函数的使用范围。

所以 uncurrying更常见的用法是对 Javascript 内置的其他方法的 借调 而不用自己都去实现一遍。

文字描述比较绕,还是继续看代码:

var test="a,b,c";
console.log(test.split(","));

var split=uncurrying(String.prototype.split);  //[ 'a', 'b', 'c' ]
console.log(split(test,','));          //[ 'a', 'b', 'c' ]

split=uncurrying(String.prototype.split) 给 uncurrying 传入一个具体的fn,即String.prototype.split ,split 函数就具有了 String.prototype.split 的功能,函数调用 split(test,',') 时,传入的第一个参数为 split 执行的上下文,剩下的参数相当于传给原 String.prototype.split 函数。

再看一个例子:

var $ = {};
console.log($.push);             // undefined
var pushUncurrying = uncurrying(Array.prototype.push);
$.push = function (obj) {
  pushUncurrying(this,obj);
};
$.push('first');
console.log($.length);            // 1
console.log($[0]);              // first
console.log($.hasOwnProperty('length'));   // true

这里模仿了一个“类似jquery库” 实现时借用 Array 的 push 方法。 我们知道对象是没有 push 方法的,所以 console.log(obj.push) 返回 undefined,可以借用Array 来处理 push,由原生的数组方法(js引擎)来维护 伪数组对象的 length 属性和数组成员。

同样的道理,我们还可以继续有:

var indexof=uncurrying(Array.prototype.indexOf);
$.indexOf = function (obj) {
  return indexof(this,obj);
};
$.push("second");
console.log($.indexOf('first'));       // 0
console.log($.indexOf('second'));       // 1
console.log($.indexOf('third'));       // -1

例如我们在实现自己的类库时,有些方法如果有些方法和原生的类似,那么可以通过 uncurrying 借用原生方法。

我们还可以把 Function.prototype.call/apply 方法 uncurring,例如:

var call= uncurrying(Function.prototype.call);
var fn= function (str) {
  console.log(this.value+str);
};
var obj={value:"Foo "};
call(fn, obj,"Bar!");            // Foo Bar!

这样可以非常灵活地把函数也当做一个普通“数据”来使用,有函数式编程的赶脚,在一些类库中经常能看到这样的用法。

通用 uncurrying 函数的进击

上面的 uncurrying 函数是比较符合思维习惯容易理解的版本,接下来一路进击,看几个其他版本:

首先,如果B格高一点,uncurrying 也可能写成这样:

var uncurrying= function (fn) {
  return function () {
    var context=[].shift.call(arguments);
    return fn.apply(context,arguments);
  }
};

当然如果还需要再提升B格,那么还可以是这样:

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
3Z版基于jquery的图片复选框(asp.net+jquery)
Apr 12 Javascript
jquery中获取元素里某一特定子元素的代码
Dec 02 Javascript
JS实现的新浪微博大厅文字内容滚动效果代码
Nov 05 Javascript
jquery编写Tab选项卡滚动导航切换特效
Jul 17 Javascript
jquery选择器中的空格与大于号>、加号+与波浪号~的区别介绍
Jun 24 Javascript
Bootstrap Table的使用总结
Oct 08 Javascript
详解js的六大数据类型
Dec 27 Javascript
JavaScript实现星星等级评价功能
Mar 22 Javascript
基于iview的router常用控制方式
May 30 Javascript
通过实例解析js简易模块加载器
Jun 17 Javascript
Node使用Nodemailer发送邮件的方法实现
Feb 24 Javascript
微信小程序用canvas画图并分享
Mar 09 Javascript
js编写简单的聊天室功能
Aug 17 #Javascript
Angular使用 ng-img-max 调整浏览器中的图片的示例代码
Aug 17 #Javascript
Canvas放置反弹效果随机图形(实例)
Aug 17 #Javascript
js实现方块上下左右移动效果
Aug 17 #Javascript
JavaScript中一些特殊的字符运算
Aug 17 #Javascript
在 Angular 中使用Chart.js 和 ng2-charts的示例代码
Aug 17 #Javascript
JS 中LocalStorage和SessionStorage的使用
Aug 17 #Javascript
You might like
php计算年龄精准到年月日
2015/11/17 PHP
Ajax+Jpgraph实现的动态折线图功能示例
2019/02/11 PHP
限制复选框的最大可选数
2006/07/01 Javascript
两个DIV等高的JS的实现代码
2007/12/23 Javascript
json跟xml的对比分析
2008/06/10 Javascript
JavaScript 继承详解(四)
2009/07/13 Javascript
JavaScript学习点滴 call、apply的区别
2010/10/22 Javascript
javascript学习笔记(十三) js闭包介绍(转)
2012/06/20 Javascript
javascript 三种方法实现获得和设置以及移除元素属性
2013/03/20 Javascript
js拖动div 当鼠标移动时整个div也相应的移动
2013/11/21 Javascript
jQuery DOM操作实例
2014/03/05 Javascript
使用CSS3的scale实现网页整体缩放
2014/03/18 Javascript
JavaScript实现横向滑出的多级菜单效果
2015/10/09 Javascript
Bootstrap网格系统详解
2016/04/26 Javascript
微信公众号-获取用户信息(网页授权获取)实现步骤
2016/10/21 Javascript
基于js 本地存储(详解)
2017/08/16 Javascript
React学习笔记之列表渲染示例详解
2017/08/22 Javascript
vue 简单自动补全的输入框的示例
2018/03/12 Javascript
微信小程序canvas拖拽、截图组件功能
2018/09/04 Javascript
vue router带参数页面刷新或回退参数消失的解决方法
2019/02/27 Javascript
原生js代码能实现call和bind吗
2019/07/31 Javascript
基于javascript实现贪吃蛇经典小游戏
2020/04/10 Javascript
[02:43]2018DOTA2亚洲邀请赛主赛事首日TOP5
2018/04/04 DOTA
[01:18:33]Secret vs VGJ.S Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
[01:16:28]DOTA2-DPC中国联赛 正赛 iG vs Magma BO3 第二场 2月23日
2021/03/11 DOTA
wxpython学习笔记(推荐查看)
2014/06/09 Python
Python读写ini文件的方法
2015/05/28 Python
Python操作csv文件实例详解
2017/07/31 Python
python多线程共享变量的使用和效率方法
2019/07/16 Python
在keras中model.fit_generator()和model.fit()的区别说明
2020/06/17 Python
详解Canvas事件绑定
2018/06/27 HTML / CSS
HTML5页面嵌入小程序没有返回按钮及返回页面空白的问题
2020/05/28 HTML / CSS
将世界上最美丽的摄影作品转化为艺术作品:Photos.com
2017/11/28 全球购物
S’well Bottle保温杯官网:绝缘不锈钢水瓶
2018/05/09 全球购物
重大事项社会稳定风险评估方案
2014/06/15 职场文书
证券公司客户经理岗位职责
2015/04/09 职场文书