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 相关文章推荐
6款新颖的jQuery和CSS3进度条插件推荐
Mar 05 Javascript
javascript实现避免页面按钮重复提交
Jan 08 Javascript
jquery滚动特效集锦
Jun 03 Javascript
jQuery可见性过滤器:hidden和:visibility用法实例
Jun 24 Javascript
AngularJS 中的指令实践开发指南(一)
Mar 20 Javascript
浅谈js script标签中的预解析
Dec 30 Javascript
Angular4开发解决跨域问题详解
Aug 28 Javascript
js实现会跳动的日历效果(完整实例)
Oct 18 Javascript
p5.js入门教程之鼠标交互的示例
Mar 16 Javascript
Vue-router的使用和出现空白页,路由对象属性详解
Sep 03 Javascript
详解Ant Design of React的安装和使用方法
Dec 27 Javascript
JavaScript实现随机点名器实例详解
May 07 Javascript
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
php中几种常见安全设置详解
2010/04/06 PHP
linux系统上支持php的 iconv()函数的方法
2011/10/01 PHP
解析PHP自带的进位制之间的转换函数
2013/06/08 PHP
PHP魔术方法__GET、__SET使用实例
2014/11/25 PHP
php操作xml并将其插入数据库的实现方法
2016/09/08 PHP
JScript 脚本实现文件下载 一般用于下载木马
2009/10/29 Javascript
jQuery对象与DOM对象之间的转换方法
2010/04/15 Javascript
jquery validate.js表单验证的基本用法入门
2010/05/13 Javascript
纯CSS打造的导航菜单(附jquery版)
2010/08/07 Javascript
制作jquery遮罩层效果导航菜单代码分享
2013/12/25 Javascript
jquery.form.js用法之清空form的方法
2014/03/07 Javascript
IE下双击checkbox反应延迟问题的解决方法
2014/03/27 Javascript
Jquery之Bind方法参数传递与接收的三种方法
2014/06/24 Javascript
JavaScript中的索引数组、关联数组和静态数组、动态数组讲解
2014/11/08 Javascript
jQuery中hover与mouseover和mouseout的区别分析
2015/12/24 Javascript
Bootstrap栅格系统的使用和理解2
2016/12/14 Javascript
如何提高javascript加载速度
2016/12/26 Javascript
jQuery源码分析之sizzle选择器详解
2017/02/13 Javascript
微信小程序 合法域名校验出错详解及解决办法
2017/03/09 Javascript
浅谈ajax在jquery中的请求和servlet中的响应
2018/01/22 jQuery
vue源码nextTick使用及原理解析
2019/08/13 Javascript
对numpy中的transpose和swapaxes函数详解
2018/08/02 Python
浅谈python 导入模块和解决文件句柄找不到问题
2018/12/15 Python
详解pandas安装若干异常及解决方案总结
2019/01/10 Python
python实现植物大战僵尸游戏实例代码
2019/06/10 Python
Python中base64与xml取值结合问题
2019/12/22 Python
如何定义一个可复用的服务
2014/09/30 面试题
英语简历自我评价
2014/01/26 职场文书
安全生产检查通报
2014/01/29 职场文书
个人求职自荐信范文
2014/06/20 职场文书
初中毕业感言300字
2015/07/31 职场文书
2016教师国培研修感言
2015/12/08 职场文书
2016特色励志班级口号
2015/12/24 职场文书
干货分享:推荐信写作技巧!
2019/06/21 职场文书
Vue接口封装的完整步骤记录
2021/05/14 Vue.js
Python3 多线程(连接池)操作MySQL插入数据
2021/06/09 Python