你的位置:首页 > Java教程

[Java教程][Effective JavaScript 笔记]第19条:熟练掌握高阶函数


高阶函数介绍

高阶函数曾经是函数式编程的一个概念,感觉是很高深的术语。但开发简洁优雅的函数可以使代码更加简单明了。过去几年中脚本语言采用了这些个技术,揭开了函数式编程的最佳惯用法的神秘面纱。
高阶函数就是将函数作为参数或返回值的函数。
将函数做为参数(通常称为回调函数)是一种强大、富有表现力的惯用法,在JS中也大量使用。

一个例子

function compareNumbers(x,y){  if(x<y){   return -1;  }  if(x>y){   return 1;  }  return 0;}[3,1,3,1,5,9].sort(compareNumbers);//[1,1,3,4,5,9]

在标准库的sort方法需要调用者传递一个具有compare方法的对象,但只有一个方法是必须的,所以直接传递一个函数更为简洁。

这里说的调用者传递一个具有compare方法的对象,测试好像没用,测试代码如下:

[3,1,3,1,5,9].sort({compare:function(a,b){return b-a;}});//[3,1,3,1,5,9]

返回的结果并不正确,不知道这里为何?

数组的sort方法

在高3中对数组的sort方法里是这样说的,sort()方法可以接收一个比较函数作为参数,以便指定哪个值位于哪个值的前面。
比较函数接收两个参数,如果第一个参数应该位于第二个参数之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个参数之后则返回一个正数。
对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用更简单的比较函数。

function compare(a,b){return b-a;}

许多数组的常见操作包含值得我们熟悉掌握的亲切的高阶函数抽象。

示例:有一个简单的转换字符串数组的操作。

var names=['Fred','Wilma','Pebbles'];var upper=[];for(var i=0,n=names.length;i<n;i++){  upper[i]=names[i].toUpperCase();}upper;//['FRED','WILMA','PEBBLES']

数组的map方法

高3中对数组的map方法的描述是:对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
上面的代码可以改写为

var names=['Fred','Wilma','Pebbles'];var upper=names.map(function(name){  return name.toUpperCase();});upper;//['FRED','WILMA','PEBBLES']

手动编写自己的高阶函数

需要引入高阶函数抽象的信号是出现重复或相似的代码。
示例:
假如我们发现程序的部分代码段使用英文字母构造一个字符串。

var aIndex='a'.charCodeAt(0);//97var alphabet='';for(var i=0;i<26;i++){alphabet+=String.fromCharCode(aIndex+i);}alphabet;//"abcdefghijklmnopqrstuvwxyz"

同时,有一段生成包含数字的字符串

var digits='';for(var i=0;i<10;i++){  digits+=i;}digits;//"0123456789"

另外其他地方还存在创建随机字符串的代码

var random='';var aIndex='a'.charCodeAt(0);//97for(var i=0;i<8;i++){   random+=String.fromCharCode(Math.floor(Math.random()*26)+aIndex)}random;

以上三段代码都创建了一个不同的字符串,但它们都有着共同的逻辑。每个循环通过连接每个独立部分的计算结果来创建一个字符串。可以把共用的部分进行提取。代码如下:

function buildString(n,callback){  var res='';  for(var i=0;i<n;i++){    res+=callback(i);  }  return res;}

上面三段代码,结果可以使用这个工具来进行创建。

var aIndex='a'.charCodeAt(0);//97var alphabet=buildString(26,function(i){  return String.fromCharCode(aIndex+i);});var digits=buildString(10,function(i){  return i;});var random=buildString(8,function(){  return String.fromCharCode(Math.floor(Math.random()*26)+aIndex);})

从这里可以看出创建高阶函数节约了很多代码。

创建高阶函数的好处

1、正确地获取循环边界条件,可以放置在高阶函数的实现中。
2、可以一次性地修改所有逻辑上的错误,不必去搜索散布在程序中的该编码模式的所有实例。
3、可以方便优化操作,因为代码抽象出来,可以只修改一处。
4、可以给高阶函数抽象一个清晰的名称,可以使代码的功能更清晰,而不需要深入细节。
当发现自己在重复地写一些相同的模式时,可以借助于高阶函数使代码更简洁、更高效、更可读。留意一些常见的模式并将它们移到高阶的工具函数中是一个重要的开发习惯。

提示

- 高阶函数是那些将函数作为参数或返回值的函数
- 熟悉掌握现有库中的高阶函数
- 学会发现可以被高阶函数所取代的常见的编码模式

附录一:数组的高阶函数方法

除以下提供的sort和map方法,还有以下几种方法。(主要的描述都摘自高3,而且除sort外,其它方法ES5中才有)
注:reverse方法并不能接收函数,所以并不是高阶函数方法,它只是简单都数组项进行反转,并不对项进行排列。

1、every()方法

对数组中的每一项运行给定的函数,如果该函数对每一项都返回true,则返回true。
示倒:

var numbers=[1,2,3,4,5,4,3,2,1];var res1=numbers.every(function(item,index,arr){   return item>0;});var res2=numbers.every(function(item,index,arr){   return item>2;});var res3=numbers.every(function(item,index,arr){   return item>5;});res1;//trueres2;//falseres3;//false

 

2、some()方法

对数组中的每一项运行给定的函数,如果函数对于任一项返回true,就会返回true。

var numbers=[1,2,3,4,5,4,3,2,1];var res1=numbers.some(function(item,index,arr){   return item>0;});var res2=numbers.some(function(item,index,arr){   return item>2;});var res3=numbers.some(function(item,index,arr){   return item>5;});res1;//trueres2;//trueres3;//false

  

3、filter()方法

对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。

var numbers=[1,2,3,4,5,4,3,2,1];var res1=numbers.filter(function(item,index,arr){   return item>0;});var res2=numbers.filter(function(item,index,arr){   return item>2;});var res3=numbers.filter(function(item,index,arr){   return item>5;});res1;//[1,2,3,4,5,4,3,2,1]res2;//[3,4,5,4,3]res3;//[]

  

4、forEach()方法

对数组中的每一项运行给定的函数。这个方法没有返回值。

var numbers=[1,2,3,4,5,4,3,2,1];var res2=[];var res1=numbers.forEach(function(item,index,arr){   res2[index]=item*2;});res1;//undefinedres2;//[2, 4, 6, 8, 10, 8, 6, 4, 2]

  

5、reduce()方法

迭代数组的所有项,然后构建一个最终的返回的值。从数组的第一项开始,逐个遍历到最后。接收两个参数:一个是每一项上调用的函数和(可选)作为归并基础的初始值。

var numbers=[1,2,3,4,5,4,3,2,1];var res1=numbers.reduce(function(prev,cur,index,arr){   return prev+cur});var res2=numbers.reduce(function(prev,cur,index,arr){   return prev+cur},100);res1;//25res2;//125

  

6、reduceRight()方法

迭代数组的所有项,然后构建一个最终的返回的值。从数组的最后一项开始,逐个遍历到第一项。接收两个参数:一个是每一项上调用的函数和(可选)作为归并基础的初始值。

var numbers=[1,2,3,4,5,4,3,2,1];var res1=numbers.reduceRight(function(prev,cur,index,arr){   return prev+cur});var res2=numbers.reduceRight(function(prev,cur,index,arr){   return prev+cur},100);res1;//25res2;//125

reduce()方法和reduceRight()方法,主要取决于要从哪头开始遍历数组。除些之外,它们完全相同。