Javascript在设计之初,参考了很多scheme语言的特性。而scheme是函数式语言鼻祖lisp的2大方言之一,所以javascript也拥有一些函数式语言的特性,包括高阶函数,闭包,lambda表达式等。
当javascript中的函数返回另一个函数,此时会形成一个闭包,而在闭包中就可以保存第一次运算的参数,我们用这个思想,来写一个通用的currying函数。
我们约定, 当传入参数时候, 继续currying化, 参数为空时才开始求值.
假设在实现一个计算每月花费的函数, 每天结束前我们都要记录今天花了多少钱, 但我们只关心月底的花费总值, 无需每天都计算一次.
使用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对象。
真正的实现代码其实很简单:
这段代码虽然很短, 初次理解的时候还是有点费力. 我们拿push的例子看看它发生了什么.
复制代码 代码如下:
var push = Array.prototype.push.uncurrying();
push( obj, ‘first' );
您可能感兴趣的文章: