要点
- 函数是值,这个值就是函数引用
- 函数是一等公民:函数引用是一等值
- 可将函数引用赋给变量、含在数据结构(如对象)中、传递给其他函数或从其他函数返回
函数是一等公民
不要再认为函数是特殊的,有别于JavaScript中的其他值
将函数视为值(这个值就是函数引用),可带来很大的好处,接下来将说明其中的原因
一等值(First-class):在编程语言中,可像对待其他任何值一样对待的值。
满足下列三个条件,就可称为一等值:
- 可以将其赋给变量
- 可以将其传递给函数
- 可以从函数中返回它们
显然,对于数字、字符串、布尔值和对象等值,它们是一等值。
事实上,函数也是一等值。在JavaScript中,其他值能做的事,函数都能做
函数作为一等值到底意味着什么?
1. 可以将函数赋给变量
2. 可以将函数传递给函数
3. 可以从函数中返回函数实现以上三点时,本质上传递的都是函数引用
关于第1点,详见JavaScript学习笔记——函数 Part1:“函数是值”
接下来将介绍后面的2、3两点
函数的妙用:向函数传递函数
例一
数组的sort方法(sort方法-学习笔记)就是利用了向函数传递函数
例二
考虑一个由对象组成的数组:
var passengers = [
{ name: "Jane Doloop", paid: true, ticket: "coach" },
{ name: "Dr. Evel", paid: true, ticket: "firstclass" },
{ name: "Sue Property", paid: false, ticket: "firstclass" },
{ name: "John Funcall", paid: true, ticket: "premium" } ];
目标:编写一些代码来检查这个乘客列表,满足条件后才允许航班起飞
例如,确保没有乘客在禁飞名单上;确保所有乘客都已买票;确保乘客健康状况良好
另外,还想将航班的乘客名单打印出来。
对于上述每项任务,通常的做法是各自为每个检查条件(检查禁飞名单、检查已买票)编写一个函数
但这些函数有大量重复:大致结构都相似,都是遍历每个乘客并进行处理
缺点:
- 有很多代码重复:这些函数都遍历所有的乘客,并对每位乘客进行处理。如果还要检查乘客是否关闭了笔记本电脑、是否要从经济舱调到头等舱等等呢?这将编写大量重复的代码
- 更糟糕的是,如果将存储乘客的数据结构从简单的对象数组改成了其他东西呢?在这种情况下,可能必须重新编写每个函数
当多个函数的代码有相似部分,可以将它们合并为一个函数,传入不同参数(可以是普通变量,也可以是函数,这里关注传入函数的情况)实现不同功能:
- 保留共有的相似代码;
- 而有差异的那部分代码,用于实现各自不同的功能,这部分代码可以由参数控制,这里的“参数”就是函数引用(向函数传递函数)
这样的优点是:修改和增添功能时更灵活,将代码拆分为多个简单的部分,减小了复杂性,减少引入bug的可能性
解决方法:编写一个知道如何遍历乘客的函数,并向它传递一个知道如何执行检查的函数(检查单个乘客是否在禁飞名单上、是否已买票等)。
//passengers传入一个对象数组
//test传入一个函数引用,指向一个能判断乘客是否满足某个条件的函数
function processPassengers(passengers, test) {
for (var i = 0; i < passengers.length; i++) {//所需的共同部分:遍历乘客
if (test(passengers[i])) {//不同部分:判断乘客是否满足特定某个条件
return false;
}
}
return true;
}
//-------------------传入的test函数可以是下面这些函数-------------------------
function checkNoFlyList(passenger) {//可编写判断其他条件的函数,以同样的方法使用
return (passenger.name === "Dr. Evel");
}
//更多不同的判断功能
//function checkNotPaid(passenger) {...}
//function printPassenger(passenger) {...}
//----------------最终对于检查禁飞名单的代码如下(其他检查项目代码类似)-----------
var allCanFly = processPassengers(passengers, checkNoFlyList);
if (!allCanFly) {
console.log("The plane can't take off: a passenger on the no fly list.");
}
//更多不同的判断功能
//var allCanFly = processPassengers(passengers, checkNotPaid);
//var allCanFly = processPassengers(passengers, printPassenger);
...
函数的妙用:从函数返回函数
编写代码,实现空乘人员提供的服务
function serveCustomer(passenger) {
getDrinkOrder();//供应饮料
//getDinnerOrder();// 供应晚餐
getDrinkOrder();//供应饮料
// 播放电影
getDrinkOrder();//供应饮料
//pickupTrash();// 清理垃圾
}
根据舱位不同,需要提供的服务通常有所不同
头等舱乘客可以点鸡尾酒或葡萄酒,而经济舱可能只供应可乐和开水
如果在简单的情形下:在getDrinkOrder内使用if else
判断舱位并提供相应服务
function getDrinkOrder(passenger){
if(passenger.ticket==="firstclass"){
...}
else{
...}
缺点:
- 如果有3类、4类顾客需要区分并提供不同服务,函数代码变得复杂难懂
- 需要多次调用getDrinkOrder,其中带来了重复的
if lese
判断,在复杂的实际程序中,这样多余的重复工作将耗费额外的时间我们应该遵循一条规则:让每个函数只做一件事,并把这件事做好
当需要创建多个[细节稍有不同]的函数时
(希望生成“定制化函数”,函数内的一部分代码,根据某个参数的不同,有细微差异)
这时希望用这个参数来控制生成不同的函数,可以编写一个“定制化函数生成器”(从函数返回函数)
“定制化函数生成器”也是一个函数(返回值为函数引用),从而实现根据接收的参数,返回不同的“定制化函数”
解决方法:
getDrinkOrder
改为“定制化函数”,从对于不同类型的顾客,提供不同的定制化服务。该“定制化函数”的生成器为createDrinkOrder
- 只获取一次旅客的舱位并用它做判断:用舱位作为参数传入
createDrinkOrder
,从而根据舱位的不同,返回不同的“定制化”函数
function serveCustomer(passenger) {
var getDrinkOrder = createDrinkOrder(passenger);
//createDrinkOrder返回一个“定制化”的函数
getDrinkOrder();//供应饮料
//getDinnerOrder();// 供应晚餐
getDrinkOrder();//供应饮料
// 播放电影
getDrinkOrder();//供应饮料
//pickupTrash();// 清理垃圾
}
//“定制化函数”的生成器,passenger的舱位不同,返回的函数就不同
function createDrinkOrder(passenger) {
var orderFunction;//定义一个变量作为返回值,它保存了函数引用
if (passenger.ticket === "firstclass") {
orderFunction = function() {
alert("Would you like a cocktail or wine?");
};
} else if (passenger.ticket === "premium") {
orderFunction = function() {
alert("Would you like wine, cola or water?");
};
} else {
orderFunction = function() {
alert("Your choice is cola or water.");
};
}
return orderFunction;//从函数返回函数
}
对于上面的代码,执行时只会经历一次旅客仓位的判断(在定制化函数生成器createDrinkOrder
内),之后在需要时,直接调用定制化函数,无需再做重复的舱位判断
另外注意,在主函数serveCustomer
中,调用“生成器”createDrinkOrder
只是获得了一个“定制化”函数(准确的说是函数引用),我们将它赋给getDrinkOrder
,要让这个“定制化”函数工作(让顾客点饮料),必须调用该函数,即getDrinkOrder();
问:既然函数作为一等公民时用途那么大,为何其他语言不支持它?
答:实际上支持(当前不支持的也正考虑这样做)。
例如,Scheme和Scala语言支持将函数作为一等公民;而PHP、最新的Java、C#和Objective-C等语言在一定程度上也支持将函数作为一等公民。
随着越来越多的人认识到将函数作为一等公民在编程语言中的价值,将有更多的语言提供这样的支持。然而,每种语言支持的方式都稍有不同,因此当探索其他语言的这种功能时,要有心理准备。