javascript中有趣的反柯里化深入分析


Posted in Javascript onDecember 05, 2012

写在前面的话:国内对前端的研究在某些方面也不逊色于国外,这篇文章虽然看不太懂,但我很欣赏这种深入研究的精神!

反科里化的话题来自javascript之父Brendan Eich去年的一段twitter. 近几天研究了一下,觉得这个东东非常有意思,分享一下。先忘记它的名字,看下它能做什么.

javascript中有趣的反柯里化深入分析

不要小看这个功能,试想下,我们在写一个库的时候,时常会写这样的代码,拿webQQ的Jx库举例。

javascript中有趣的反柯里化深入分析
我们想要的,其实只是借用Array原型链上的一些函数。并没有必要去显式的构造一个新的函数来改变它们的参数并且重新运算。

如果用uncurrying的方式显然更加优雅和美妙,就像这样:

javascript中有趣的反柯里化深入分析

还能做很多有趣和方便的事情.

javascript中有趣的反柯里化深入分析

javascript中有趣的反柯里化深入分析

甚至还能把call和apply方法都uncurrying,把函数也当作普通数据来使用. 使得javascript中的函数调用方式更像它的前生scheme, 当函数名本身是个变量的时候, 这种调用方法特别方便.

scheme里面调用函数是这样:
javascript中有趣的反柯里化深入分析

javascript里可以写的很接近.
javascript中有趣的反柯里化深入分析

再看看jquery库,由于jquery对象( 即通过$()创建的对象 )是一个对象冒充的伪数组,它有length属性,并且能够通过下标查找对应的元素,当需要给jquery对象添加一个成员时, 伪代码大概是:

javascript中有趣的反柯里化深入分析

如果用uncurrying的话, 就可以

javascript中有趣的反柯里化深入分析

借用了array对象的push函数, 让引擎去自动管理数组成员和length属性.

而且可以一次把需要的函数全部借过来, 一劳永逸. 一段测试代码:

javascript中有趣的反柯里化深入分析

总的来说, 使用uncurrying技术, 可以让任何对象拥有原生对象的方法. 好了,如果到这里依然没有引起你的兴趣,那么你可以去干点别的了。

接下来一步一步来看看原理以及实现。
在了解反currying化这个奇怪的名字之前,我们得先搞清楚currying。

维基百科上的定义:科里化( currying ); 又称部分求值,是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数并且返回结果的新函数的技术。

通俗点讲,currying有点类似买房子时分期付款的方式,先给一部分首付( 一部分参数 ), 返回一个存折( 返回一个函数 ),合适的时候再给余下的参数并且求值计算。

来看看我们都用过的currying, 我们经常在绑定context 的时候实现一个Function.prototype.bind函数.

javascript中有趣的反柯里化深入分析

高阶函数是实现currying的基础, 所谓高阶函数至少满足这2个特性:
1,函数可以当作参数传递,
2,函数可以作为返回值。

Javascript在设计之初,参考了很多scheme语言的特性。而scheme是函数式语言鼻祖lisp的2大方言之一,所以javascript也拥有一些函数式语言的特性,包括高阶函数,闭包,lambda表达式等。

当javascript中的函数返回另一个函数,此时会形成一个闭包,而在闭包中就可以保存第一次运算的参数,我们用这个思想,来写一个通用的currying函数。

javascript中有趣的反柯里化深入分析

我们约定, 当传入参数时候, 继续currying化, 参数为空时才开始求值.

假设在实现一个计算每月花费的函数, 每天结束前我们都要记录今天花了多少钱, 但我们只关心月底的花费总值, 无需每天都计算一次.

javascript中有趣的反柯里化深入分析

使用currying函数, 便可以延迟到最后一刻才一起计算, 好处不言而喻, 在很多场合可以避免无谓的计算, 节省性能, 也是实现惰性求值的一种方案.

好了,现在才走进正题,

curring是预先填入一些参数.

反curring就是把原来已经固定的参数或者this上下文等当作参数延迟到未来传递.

其实就是搞这样一个事情,将:

obj.foo( arg1 ) //foo本来是只在obj上的函数. 就像push原本只在Array.prototype上

转化成这样的形式

foo( obj, arg1 ) // 跟我们举的第一个例子一样.将[].push转换成push( [] )

就像原本是接在电视插头上的插座,把它拆下来之后,其实也能用来接冰箱。

Ecma上Array和String的每个原型方法后面都有这么一段话,比如push:

NOTE The push function is intentionally generic; it does not require that its this value be an Array object.
Therefore it can be transferred to other kinds of objects for use as a method. Whether the concat function can be applied.

Javascript为什么要这样设计, 我们先来复习下动态语言中重要的鸭子类型思想.

说个故事:

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

这个就是鸭子类型的概念,在javascript里面,很多函数都不做对象的类型检测,而是只关心这些对象能做什么。

Array构造器和String构造器的prototype上的方法就被特意设计成了鸭子类型。这些方法不对this的数据类型做任何校验。这也就是为什么arguments能冒充array调用push方法.

看下v8引擎里面Array.prototype.push的代码:

function ArrayPush() { 
var n = TO_UINT32( this.length ); 
var m = %_ArgumentsLength(); 
for (var i = 0; i < m; i++) { 
this[i+n] = %_Arguments(i); //属性拷贝 
this.length = n + m; //修正length 
return this.length; 
} 
}

可以看到,ArrayPush方法没有对this的类型做任何显示的限制,所以理论上任何对象都可以被传入ArrayPush这个访问者。

我们需要解决的只剩下一个问题, 如何通过一种通用的方式来使得一个对象可以冒充array对象。

真正的实现代码其实很简单:

javascript中有趣的反柯里化深入分析

这段代码虽然很短, 初次理解的时候还是有点费力. 我们拿push的例子看看它发生了什么.

var push = Array.prototype.push.uncurrying(); push( obj, ‘first' );

javascript中有趣的反柯里化深入分析

Javascript 相关文章推荐
Javascript 布尔型分析
Dec 22 Javascript
JavaScript中常用的运算符小结
Jan 18 Javascript
Java/JS获取flash高宽的具体方法
Dec 27 Javascript
浅析$.getJSON异步请求和同步请求
Jun 06 Javascript
如何利用JSHint减少JavaScript的错误
Aug 23 Javascript
JS多文件上传的实例代码
Jan 11 Javascript
bootstrap如何让dropdown menu按钮式下拉框长度一致
Apr 10 Javascript
JavaScript之RegExp_动力节点Java学院整理
Jun 29 Javascript
jQuery AJAX与jQuery事件的分析讲解
Feb 18 jQuery
推荐几个不错的console调试技巧实现
Dec 20 Javascript
浅谈JavaScript浅拷贝和深拷贝
Nov 07 Javascript
如何vue使用el-table遍历循环表头和表体数据
Apr 26 Vue.js
js multiple全选与取消全选实现代码
Dec 04 #Javascript
在js(jquery)中获得文本框焦点和失去焦点的方法
Dec 04 #Javascript
关于javascript中的typeof和instanceof介绍
Dec 04 #Javascript
无缝滚动改进版支持上下左右滚动(封装成函数)
Dec 04 #Javascript
js动画(animate)简单引擎代码示例
Dec 04 #Javascript
JavaScript中“+”的陷阱深刻理解
Dec 04 #Javascript
将光标定位于输入框最右侧实现代码
Dec 04 #Javascript
You might like
分页显示Oracle数据库记录的类之二
2006/10/09 PHP
如何在Ubuntu下启动Apache的Rewrite功能
2013/07/05 PHP
PHP封装的完整分页类示例
2018/08/21 PHP
PHP按一定比例压缩图片的方法
2018/10/12 PHP
JScript内置对象Array中元素的删除方法
2007/03/08 Javascript
经常用的图片在容器中的水平垂直居中实例
2007/06/10 Javascript
jQuery源码中的chunker 正则过滤符分析
2012/07/31 Javascript
JavaScript 判断用户输入的邮箱及手机格式是否正确
2013/12/08 Javascript
购物车选中得到价格实现示例
2014/01/26 Javascript
jQuery的css() 方法使用指南
2015/05/03 Javascript
将页面table内容与样式另存成excel文件的方法
2015/08/05 Javascript
js实现常用排序算法
2016/08/09 Javascript
Angular.JS判断复选框checkbox是否选中并实时显示
2016/11/30 Javascript
浅析Ajax语法
2016/12/05 Javascript
jQuery实现的简单前端搜索功能示例
2017/10/28 jQuery
利用JQUERY实现多个AJAX请求等待的实例
2017/12/14 jQuery
python端口扫描系统实现方法
2014/11/19 Python
在Python程序中进行文件读取和写入操作的教程
2015/04/28 Python
mac下给python3安装requests库和scrapy库的实例
2018/06/13 Python
Python3.7实现中控考勤机自动连接
2018/08/28 Python
Python实现定时执行任务的三种方式简单示例
2019/03/30 Python
Python socket模块实现的udp通信功能示例
2019/04/10 Python
Python利用scapy实现ARP欺骗的方法
2019/07/23 Python
CSS3制作皮卡丘动画壁纸的示例
2020/11/02 HTML / CSS
HTML5+CSS3实现无插件拖拽上传图片(支持预览与批量)
2017/01/05 HTML / CSS
vue实现倒计时功能
2021/03/24 Vue.js
电子商务应届生求职信
2013/11/16 职场文书
修理厂厂长岗位职责
2014/01/30 职场文书
电工工作职责范本
2014/02/22 职场文书
个人收入证明模板
2014/09/18 职场文书
小学语文教学随笔
2015/08/14 职场文书
MySQL创建高性能索引的全步骤
2021/05/02 MySQL
HTML5简单实现添加背景音乐的几种方法
2021/05/12 HTML / CSS
教你怎么用python selenium实现自动化测试
2021/05/27 Python
使用Bandicam录制鼠标指针并附带点击声音,还可以添加点击动画效果
2022/04/11 数码科技
SpringBoot Http远程调用的方法
2022/08/14 Java/Android