《JS权威指南学习总结--8.8.3 不完全函数》
内容要点:
本节讨论的是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数(partial function),每次函数调用叫做不完全调用(partial application),这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止,举一个简单的例子,将对函数f(1,2,3,4,5,6)的调用修改为等价的f(1,2)(3,4)(5,6),后者包含三次调用,和每次调用相关的函数就是"不完全函数"。
一.
1.函数f()的bind()方法返回一个新函数,给新函数传入特定的上下文和一组指定的参数,然后调用f()。
我们说它把函数 "绑定至" 对象并传入一部分参数。bind()方法只是将实参放在(完整实参列表的)左侧,也就是说传入bind()的实参都是放在传入原始函数的实参列表开始的位置,但有时我们期望将传入bind()的实参放在(完整实参列表的)右侧:
//实现一个工具函数将类数组对象(或对象)转换为真正的数组
//在后面的示例代码中用到了这个方法将arguments对象转换为真正的数组
function array(a,n){ return Array.prototype.slice.call(a,n||0); }
//这个函数的实参传递至左侧
function partialLeft(f /*,...*/){
var args = arguments; //保存外部的实参数组
return function(){ //并返回这个函数
var a= array(args,1); //开始处理外部的第1个args
a = a.concat(array(arguments)); //然后增加所有的内部实参
return f.apply(this,a); //然后基于这个实参列表调用f()
};
}
//这个函数的实参传递至右侧
function partialRight(f/*,...*/){
var args = arguments; //保存外部实参数组
return function(){
var a = array(arguments); //从内部参数开始
a = a.concat(array(args,1)); //然后从外部第1个args开始添加
return f.apply(this,a); //最后基于这个实参列表调用f()
};
}
//这个函数的实参被用做模板
//实参列表中的undefined值都被填充
function partial(f/*,...*/){
var args = arguments; //保存外部实参数组
return function(){
var a = array(args,1); //从外部args开始
var i = 0,j =0;
//遍历args,从内部实参填充undefined值
for(;i<a.length;i++)
if(a[i] === undefined) a[i] = arguments[j++];
//现在将剩下的内部实参都追加进去
a = a.concat(array(arguments,j))
return f.apply(this,a);
};
}
//这个函数带有三个实参
var f = function(x,y,z){ return x*(y-z); };
//注意这三个不完全调用之间的区别
partialLeft(f,2)(3,4) //=>-2:绑定第一个实参:2*(3 - 4
partialRight(f,2)(3,4) //=>6:绑定最后一个实参:3*(4 -2)
partial(f,undefined,2)(3,4) //=>-6:绑定中间的实参:3*(2-4)
二.利用已有的函数来定义新的函数
利用这种不完全函数的编程技巧,可以编写一些有意思的代码,利用已有的函数来定义新的函数:
var increment = partialLeft(sum,1);
var cuberoot = partialRight(Math.pow,1/3);
String.prototype.first = partial(String.prototype.charAt,0);
String.prototype.last = partial(String.prototype.substr,-1,1);
当将不完全调用和其他高阶函数整合在一起的时候,事情就变得格外有趣了。比如,这里的例子定义了not()函数,它用到了刚才提到的不完全调用:
var not = partialLeft(compose,function(x){ return !x;});
var even = function(x){ return x%2 ==0; };
var odd = not(even);
var isNumber = not(isNaN);
我们也可以使用不完全调用的组合来重新组织平均数和标准差的代码,这种编程风格是非常纯粹的函数式编程:
var data = [1,1,3,5,5]; //我们要处理的数据
var sum = function(x,y){ return x+y; }; //两个初等函数
var product = function(x,y){ return x*y; };
var neg = partial(product,-1);
var square = partial(Math.pow,undefined,2);
var sqrt = partial(Math.pow,undefined, .5);
var reciprocal = partial(Math.pow,undefined,-1);
//现在计算平均值和标准差,所有的函数调用都不带运算符
var mean =product(reduce(data,sum),reciprocal(data.length));
var stddev = sqrt(product(reduce(map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1))));
console.log(mean);//datad的平均数 3
console.log(neg(mean));//平均数*(-1): -2 :mean*-1
console.log(partial(sum,neg(mean)));//sum里的y是-3:即是负的平均数
console.log(compose(square,partial(sum,neg(mean))));//sum里的y是-3:函数式:(data-3)^2,compose:组合两个函数,见8.8.2高阶函数
console.log(Array.map(data,compose(square,partial(sum,neg(mean)))));//[4, 4, 0, 4, 4]: 即是平方差
console.log(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum));//16:即是平方差的求和
console.log(reciprocal(sum(data.length,-1))); //0.25
console.log(product(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1)))); //4
console.log(sqrt(product(Array.reduce(Array.map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1)))));//2
细细体会,跟数学式子差不多!