[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);//97
var 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);//97
for(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);//97
var 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;//true
res2;//false
res3;//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;//true
res2;//true
res3;//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;//undefined
res2;//[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;//25
res2;//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;//25
res2;//125
reduce()方法和reduceRight()方法,主要取决于要从哪头开始遍历数组。除些之外,它们完全相同。
[Effective JavaScript 笔记]第19条:熟练掌握高阶函数的更多相关文章
- [Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域
嵌套函数声明.没有标准的方法在局部块里声明函数,但可以在另一个函数的顶部嵌套函数声明. function f(){return "global"} function test(x) ...
- [Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用
eval函数不仅仅是一个函数.大多数函数只访问定义它们所在的作用域,而不能访问除此之外的作用域(词法作用域).eval函数具有访问调用它时的整个作用域的能力.编译器编写者首次设法优化js时,eval函 ...
- [Effective JavaScript 笔记]第31条:使用Object.getPrototypeOf函数而不要使用__proto__属性
ES5引入Object.getPrototypeOf函数作为获取对象原型的标准API,但由于之前的很多js引擎使用了一个特殊的__proto__属性来达到相同的目的.但有些浏览器并不支持这个__pro ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法
不好的实践 函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的.方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章<理解函数调用.方法调用及构造函数调用之 ...
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
随机推荐
- IT男的”幸福”生活"续1
IT男的”幸福”生活"续1 我和LL开始下步追妹计划...... 知彼知已,方能把握机会.没想到孙子兵法太给力了,据LL了解,MM(她)是湖北人,找对象一般不会考虑外省的.高度165左右,相 ...
- CSS培训机构
CSS培训机构[跑赢职场,跑赢未来] CSS培训机构[跑赢职场,跑赢未来]CSS设计热度白热化的今天,如何选对专业的CSS设计培训学校/机构,成为众多人关注的话题.跑赢职场就是css培训机构中的佼佼者 ...
- 机器学习中的矩阵方法04:SVD 分解
前面我们讲了 QR 分解有一些优良的特性,但是 QR 分解仅仅是对矩阵的行进行操作(左乘一个酉矩阵),可以得到列空间.这一小节的 SVD 分解则是将行与列同等看待,既左乘酉矩阵,又右乘酉矩阵,可以得出 ...
- iOS开发之格式化日期时间(转)
在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来处理.例如: //实例化一个NSDateFormatter对象 NSDateForma ...
- Xdebug开源PHP程序调试器
Xdebug是一个开放源代码的PHP程序调试器(即一个Debug工具),可以用来跟踪,调试和分析PHP程序的运行状况. 本文为大家讲解的是在linux下xdebug的安装和配置方法,感兴趣的同学参考下 ...
- C基础--C语言的数组
数组的定义: 一.正确写法: 1.int ages[5]--定义了一个5个长度的int类型的数组 2.int ages[]={1,2,3,4,5};--定义了一个5个长度的int类型的数组,并且初始化 ...
- Java-ArrayList和Vector的区别
这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素, ...
- BZOJ-1226 学校食堂Dining 状态压缩DP
1226: [SDOI2009]学校食堂Dining Time Limit: 10 Sec Memory Limit: 259 MB Submit: 588 Solved: 360 [Submit][ ...
- codevs1225 八数码难题
题目描述 Description Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.问题描述 在 3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数 ...
- COGS8 备用交换机
备用交换机 ★★ 输入文件:gd.in 输出文件:gd.out 简单对比时间限制:1 s 内存限制:128 MB [问题描述] n个城市之间有通讯网络,每个城市都有通讯交换机,直接或间 ...