JavaScript学习笔记——函数 Part4:向函数传递函数、从函数返回函数(函数是一等公民)

要点

  • 函数是值,这个值就是函数引用
  • 函数是一等公民:函数引用是一等值
  • 可将函数引用赋给变量、含在数据结构(如对象)中、传递给其他函数从其他函数返回

函数是一等公民

不要再认为函数是特殊的,有别于JavaScript中的其他值
将函数视为值(这个值就是函数引用,可带来很大的好处,接下来将说明其中的原因

一等值(First-class):在编程语言中,可像对待其他任何值一样对待的值。
满足下列三个条件,就可称为一等值:

  1. 可以将其赋给变量
  2. 可以将其传递给函数
  3. 可以从函数中返回它们

显然,对于数字、字符串、布尔值和对象等值,它们是一等值。
事实上,函数也是一等值。在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等语言在一定程度上也支持将函数作为一等公民。
随着越来越多的人认识到将函数作为一等公民在编程语言中的价值,将有更多的语言提供这样的支持。然而,每种语言支持的方式都稍有不同,因此当探索其他语言的这种功能时,要有心理准备。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值