JavaScript函数式编程实战:计算平均值与标准差
本文通过JavaScript计算数组平均值与标准差的实例,深入解析函数式编程核心概念。文章对比了命令式与函数式两种写法,详细拆解了reduce、map、partial及compose等关键函数的用法与组合逻辑,帮助开发者理解如何利用高阶函数构建清晰的数据处理管道。
JavaScript函数式编程基础与实例
本文通过具体实例解析JavaScript中的函数式编程方法,旨在为读者提供可参考的代码实践与理论分析。
JavaScript与其他动态语言类似,支持高阶函数的编写。高阶函数是指能够接收函数作为参数或返回函数的函数。由于在JavaScript中函数被视为完全的对象,属于第一类公民,这为实施函数式编程提供了基础条件。
非函数式与函数式实现对比
以下示例代码源自某JavaScript教程,其目标是计算数组元素的平均值和标准差。首先展示一种非函数式(命令式)的编程实现方式:
var data = [1,1,3,5,5];
var total = 0;
for(var i = 0;i < data.length;i++)
total += data[i];
var mean = tatal/data.length; //平均数为3
//计算标准差
total = 0;
for(var i = 0;i < data.length;i++){
var deviation = data[i] - mean;
tatal += deviation * deviation;
}
var stddev = Math,.sqrt(total/(data.length-1));//标准差为2
为了采用函数式编程风格,我们需要预先定义一系列辅助函数(helper functions):
//将类数组对象转换为真正的数组
function array(a,n){
return Array.prototype.slice.call(a,n||0);
}
//将函数实参传递至左侧
function partial_left(f){
var args = arguments;
return function(){
var a = array(args,1);
a = a.concat(array(arguments));
return f.apply(this,a);
};
}
//将函数的实参传递至右侧
function partial_right(f){
var args = arguments;
return function(){
var a = array(arguments);
a = a.concat(array(args,1));
return f.apply(this,a);
};
}
//该函数实参被用做模版,
//实参列表中的undefined值会被实际实参值填充。
function partial(f){
var args = arguments;
return function(){
var a = array(args,1);
var i = 0,j = 0;
for(;i<a.length;i++)
if(a[i] === undefined)
a[i] = arguments[j++];
a = a.concat(array(arguments,j));
return f.apply(this,a);
};
}
//返回一个函数类似于f(g())
function compose(f,g){
return function(){
return f.call(this,g.apply(this,arguments));
};
}
接下来展示完全使用函数式编程风格编写的JavaScript代码:
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,0.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))));
核心函数reduce与map解析
除了reduce和map这两个核心函数外,其他辅助函数已在前面定义。其中,reduce函数的行为类似于Ruby语言中的inject函数:
ary = (1..10).to_a
ary.inject(0) {|sum,i|sum + i} //结果为55
JavaScript的具体实现代码如下:
var ary = [1,2,3,4,5,6,7,8,9,10]
ary.reduce(function(sum,i){
return sum + i;
},0);
参数0表示sum的初始值。如果省略该参数,sum将初始化为数组的第一个元素值,此处可以省略不写。
map函数的作用相对直观,它类似于对数组中的每个元素执行操作,并返回一个经过处理的新数组。以Ruby代码为例,其JavaScript实现方式与之类似:
a = (1..3).to_a; #数组[1,2,3]
a.map {|x| x*2} #返回新数组[2,4,6]
复杂函数组合逻辑深度拆解
下面我们来详细分析前面那串较长的函数式代码。
sum和product分别定义了元素相加和相乘的逻辑函数;
neg也是一个函数,其功能等价于调用product(-1, x),即对x的值求负;
square函数等价于Math.pow(x, 2),用于计算x的平方值。注意这里partial函数的第二个参数为undefined,这意味着该形参将由第一个实参填补。换句话说,square(x)的功能等同于Math.pow(x, 2)。
sqrt函数与square类似,功能等价于Math.pow(x, 0.5),即计算x的平方根。最后一个函数reciprocal也不复杂,等价于Math.pow(x, -1),即计算x的负一次方,也就是x的倒数。
接下来展示如何将上述各个函数组合在一起使用:
首先看平均值的计算,逻辑很简单:先计算数组元素的总和,然后乘以数组长度的倒数,即数组总和除以数组长度。
最后来看看似复杂的标准差计算,建议由内向外逐步分析: 首先观察包含neg的那一层逻辑:
//等价于函数sum(-1 * mean + x)
partial(sum,neg(mean)
接下来分析compose函数的作用:
//下面在源代码上做了等价替换,可以再次等价于:
//square(sum(-1*mean + x)),再次展开(我剥,我剥,我剥洋葱...):
//Math.pow(sum(-1*mean + x),2);
compose(square,sum(-1*mean + x))
随后查看map函数的执行过程:
//很清楚吧!?即data中每一个元素都为一个x,将其传入后面的函数,然后返回一个计算后的新数组,即新数组中的每个元素的值是data中的每个元素加上data负的平均数,然后对其结果计算2次方的结果。
map(data,Math.pow(sum(-1*mean + x),2))
接着看map外层的reduce函数:
//将前面新数组的每个元素值加起来。
reduce(map(...),sum)
然后查看reciprocal函数的具体表现:
//等价于求(data.length-1)的倒数
reciprocal(sum(data.length,-1))
再看外层的product函数:
//等价于新数组元素的和除以(data.length-1)
product(reduce(...),reciprocal(...))
最外层的sqrt表示对前述除法得出的结果求平方根。大家可以对照前文非函数式编程的代码,两者逻辑完全一致。看似庞大的一坨代码,展开分析后难度立即降至零。如果读者仍感困惑,那完全是表达能力的局限,欢迎提问交流。
解释完毕,代码分析到此结束。
希望本文所述的JavaScript程序设计内容能对读者有所帮助。
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
