javascript函数式编程程序员的工具集
如果你仔细看了到目前为止出现过的示例代码,你会发现这里面的一些方法不太熟悉。它们是map()、filter()和reduce()函数,它们对任何语言的函数式编程都至关重要。它们可以让你不必使用循环和语句,写出更简洁的代码。
map()、filter()和reduce()函数组成了函数式程序员工具集的核心部分,这个工具集包括一系列纯的、高阶的函数,它们是函数式方法的主力。实际上,它们是纯函数和高阶函数的典型,它们以一个函数为输入,返回一个输出结果,并且不产生副作用。
然而它们是浏览器中ECMAScript5.1的实现标准,它们只工作于数组。每次调用它们,一个新的数组会被创建并返回,而原来存在的那个数组不会被改变。它们以函数为输入,经常使用匿名函数作为回调函数。它们遍历数组,并对数组的每一个元素应用这个函数!
myArray=[1,2,3,4]; newArray=myArray.map(function(x){returnx*2}); console.log(myArray);//Output:[1,2,3,4] console.log(newArray);//Output:[2,4,6,8]
还有一点,它们只作用于数组,无法作用于其它可迭代的数据结构,比如对象。不用担心,有很多库比如Underscore.js,Lazy.js,stream.js等等都实现了它们自己的更强大的map()、filter()和reduce()。
回调
如果你以前从来没用过回调,那这个概念可能会让你有些迷惑。尤其是在Javascript中,Javascript给出了好几种声明函数的方式。
回调函数用于传递给另外一个函数供它们使用,这是一种像传递对象一样来传递逻辑的方式:
varmyArray=[1,2,3]; functionmyCallback(x){returnx+1}; console.log(myArray.map(myCallback));
对于比较简单的任务可以用匿名函数:
console.log(myArray.map(function(x){returnx+1}));
回调不仅用于函数式编程,在Javascript中它们能干很多事情。仅作为例子,这有个callback()函数用于jQuery的AJAX调用:
functionmyCallback(xhr){ console.log(xhr.status); returntrue; } $.ajax(myURI).done(myCallback);
注意这里只用了函数的名字,因为我们并不是要调用函数而是传递函数,写成这样就错了:
$.ajax(myURI).fail(myCallback(xhr)); //或者 $.ajax(myURI).fail(myCallback());
如果我们调用了函数会发生什么?在这个例子里,myCallback(xhr)会尝试执行,控制台将打印“undefined”,并会返回true。当ajax()完成调用时,它根据名字找到的回调函数将是一个"true",然后就报错了。
也就是说我们无法指定给回调函数传什么参数,如果我们的回调函数需要让ajax()函数传给他我们想要的参数,我们可以把回到函数包在一个匿名函数里:
functionmyCallback(status){ console.log(status); returntrue; } $.ajax(myURI).done(function(xhr){ myCallback(xhr.status) });
Array.prototype.map()
map()是这些函数的老大,它简单地对数组里的元素依此应用回调函数。
语法:arr.map(callback[,thisArg]);
参数:
•callback():这个函数为新数组产生一个元素,它接收的参数:◦currentValue:数组当前遍历到的元素
◦index:数组中当前元素序数
◦array:当前正在处理的数组
•thisArg:这是个可选参数,当执行回调的时候它作为回调函数的this
例子:
var integers=[1,-0,9,-8,3], numbers=[1,2,3,4], str='helloworldhowyadoing?'; //将整数映射为他们自己的绝对值 console.log(integers.map(Math.abs)); //将数组中的元素与自己的位置序数相乘 console.log(numbers.map(function(x,i){ returnx*i })); //单词隔一个变一个大写 console.log(str.split('').map(function(s,i){ if(i%2==0) returns.toUpperCase(); else returns; }));
尽管Array.prototype.map方法是Javascript中数组对象的标准方法,你也可以很容易地扩展自己的对象。
MyObject.prototype.map=function(f){ returnnewMyObject(f(this.value)); };
Array.prototype.filter()
filter()函数用于把数组中的一些元素筛选出来。回调函数必须返回真(保留到新数组里)或假(扔掉)。用map()可以做类似的事情,就是把你像扔掉的元素返回为null,不过filter()函数会在新数组里面删除这些不要的元素,而不是留个null占着位置。
语法:arr.filter(callback[,thisArg]);
•callback():这个函数用来测试数组中的每个元素,要保留返回真,否则返回假。它有这些参数:◦currentValue:数组当前遍历到的元素
◦index:数组中当前元素的序数
◦array:当前正在处理的数组
•thisArg:这是个可选参数,当执行回调的时候它作为回调函数的this
例子:
varmyarray=[1,2,3,4] words='hello123worldhow345yadoing'.split(''); re='[a-zA-Z]'; //筛选整数 console.log([-2,-1,0,1,2].filter(function(x){ returnx>0 })); //筛选所有含字母的单词 console.log(words.filter(function(s){ returns.match(re); })); //随机移除数组中的元素 console.log(myarray.filter(function(){ returnMath.floor(Math.random()*2) }));
Array.prototype.reduce()
reduce()函数,有时也称为fold,它用于把数组中的所有值聚集到一起。回调需要返回组合对象的逻辑。对于数字来说,它们往往会被加到一起或者乘到一起。对于字符串来说,它们往往是被追加到一起。
语法:arr.reduce(callback[,initialValue]);
参数
•callback():此函数把两个对象合并成一个对象,并将其返回。参数有:◦previousValue:上一次回调函数被调用时返回的值,或者是初始值(如果有的话)
◦currentValue:数组当前正在处理的元素
◦index:数组中当前元素的序数
◦array:当前正在处理的数组
•initialValue:可选。第一次回调所传入参数的初始值
例子
varnumbers=[1,2,3,4]; //把数组中所有的值加起来 console.log([1,2,3,4,5].reduce(function(x,y){ returnx+y },0)); //查找数组中最大的值 console.log(numbers.reduce(function(a,b){ returnMath.max(a,b)//max()函数只能有两个参数 }) );
其它函数
map()、filter()和reduce()函数在我们辅助函数的工具箱里并不孤单。这里还有更多的函数几乎在所有函数式应用里都会被使用。
Array.prototype.forEach
forEach()函数本质上是map()函数的非纯版本,它会遍历整个数组,并对每个元素应用回调。然而这些回调函数不返回值。它是实现for循环的一个更纯粹的方式。
语法:arr.forEach(callback[,thisArg]);
参数:
•callback():对数组中每一个元素所应用的。参数有:◦currentValue:数组中当前正在处理的元素
◦index:数组中当前元素的序数
◦array:正在处理的数组
•thisArg:可选。回调函数中作为this的值
例子:
vararr=[1,2,3]; varnodes=arr.map(function(x){ varelem=document.createElement("div"); elem.textContent=x; returnelem; }); //对每一个元素的值输出日志 arr.forEach(function(x){ console.log(x) }); //把节点追加到DOM上 nodes.forEach(function(x){ document.body.appendChild(x) });
Array.prototype.concat
如果不用for或while处理数组,你会经常需要把数组拼接起来。另一个Javascript内建函数concat就是专门干这事儿的。concat函数会返回一个新数组但不改变旧数组。它可以把你传入的所有参数拼接到一起。
console.log([1,2,3].concat(['a','b','c'])//拼接两个数组
//Output:[1,2,3,'a','b','c']
它返回两个数组拼接成的数组,同时原来的那些数组没有被改变。这就意味着concat函数可以链式调用。
vararr1=[1,2,3]; vararr2=[4,5,6]; vararr3=[7,8,9]; varx=arr1.concat(arr2,arr3); vary=arr1.concat(arr2).concat(arr3)); varz=arr1.concat(arr2.concat(arr3))); console.log(x); console.log(y); console.log(z);
变量x、y、z的值最后都是[1,2,3,4,5,6,7,8,9]。
Array.prototype.reverse
这个Javascript内建函数是用于数组变形的。reverse函数用于将一个数组反转,也就是第个一元素会跑到最后,而最后一个元素变成了第一个元素。
然而,这个函数并不会返回一个新的数组,而是把原来的数组替换掉了。我们可以做个更好的。下面是一个纯的反转数组函数
varinvert=function(arr){ returnarr.map(function(x,i,a){ returna[a.length-(i+1)]; }); }; varq=invert([1,2,3,4]); console.log(q);
Array.prototype.sort
与map()、filter()和reduce()函数相似,排序函数sort()需要传入一个回调函数来定义数组如何排序。但是,跟reverse()一样,它也会把原来的数组替换。这可不太好。
arr=[200,12,56,7,344];
console.log(arr.sort(function(a,b){returna–b}));
//arr现在是:[7,12,56,200,344];
我们可以写一个纯函数的sort(),但是排序算法的源代码很麻烦。对于特别大的数组,应当根据特定的数据结构来选用适合的算法,比如快速排序、合并排序、冒泡排序等等。
Array.prototype.every和Array.prototype.some
Array.prototype.every()和Array.prototype.some()都是纯的高阶函数,它们是Array对象的方法,通过回调函数根据数组各元素返回的布尔值(或相当于布尔的值)来进行测试。如果数组中所有的元素通过回调函数计算都返回True,every()函数就返回true;如果数组中有一个元素返回True,some()函数就返回True。
例子:
functionisNumber(n){ return!isNaN(parseFloat(n))&&isFinite(n); } console.log([1,2,3,4].every(isNumber));//Return:true console.log([1,2,'a'].every(isNumber));//Return:false console.log([1,2,'a'].some(isNumber));//Return:true