面向函数:函数做变量 / 回调 / 委托 / Lamda / 箭头函数……

更多
2021年06月01日 23点38分 作者:叶飞 修改

首先声明:“面向函数”是飞哥“自定义”的一个说法,算是一种借用,和面向过程/面向对象并列,便于记忆和理解,但并不严谨。

类似的、主流的说法是函数式编程,但和我想表达的内容还是有一些差距。


作为变量的函数

一般我们认为变量是一种数据,函数是一种操作。

数据是可以传递的,比如变量赋值、函数传参之类的。

但操作呢?操作/动作/行为能不能传递?……

随着编程语言的发展(当然根源是开发的需要),现代编程语言普遍允许将函数也视为(或类似于)一种值或变量,可以传递。

随之产生了各种各样的语法现象:

  • 回调(callback):JavaScript
  • 委托(delegate):C#
  • Lamda表达式:C#和Java
为什么需要?不搞明白这个问题,你越学越晕!


重用

需求:找出数组中满足某种条件的数。

某种条件,可以是大于0,于是你的代码就是:

	for (var i = 0; i < numbers.length; i++) {
		if(numbers[i]>0){ //就筛选条件变化着的
			console.log(numbers[i]);		
		}//else nothing
	}

也可能是奇数,那么你的代码就是:

	for (var i = 0; i < numbers.length; i++) {
		if(numbers[i]%2==1){ //就筛选条件变化着的
			console.log(numbers[i]);		
		}//else nothing
	}

注意:这两段代码我们进行了复制粘贴:

(格言:程序员厌恶复制粘贴。)

然后只改了一点点……

以后还可能是其他任何稀奇古怪的要求,比如能同时被3和5整除……

我们能不能把这些代码重用一下?

经过观察,我们发现,上述代码中唯一不同的就是筛选条件,假如我们把两段代码封装成一个方法,把筛选条件作为一个变量/参数……

PS:这就有点“封装变化”的味道了


函数变量和匿名函数

JavaScript变量就可以直接被函数赋值:

	var where = function(number){
		return number > 0;
	}
等号右边就是一个函数,只是没有名字,所以被称之为匿名函数

被函数赋值之后,where就变成了一个函数变量,实际上也就是一个函数。演示:

  • where(23)调用
  • 控制台输入where查看

既然where是变量,那么就可以被重新赋值:

	where = function(number){
		return number % 2 == 1;
	}
甚至把命名函数赋值给where也是没有问题的:(注意:是alert不是alert(),没有圆括号)

对的,JavaScript就这么任性(但强烈建议!)


回调:把函数当做参数传递

匿名函数就是“值”(同32,"源栈",[12, 32, 18, 96])一样的存在,可以被赋值给变量,也可以被直接用作参数。

综上,就可以封装这么一个filter函数:

	function filter(numbers, where){    //参数where是一个回调函数
		for (var i = 0; i < numbers.length; i++) {
			if(where(numbers[i])){    //where被作为函数直接使用
				console.log(numbers[i]);		
			}//else nothing
		}
	}

把函数作为参数,传递给另外一个函数,作为参数的函数就被称之为回调函数

调用filter时,既可以传递一个(命名)函数名,也可以是函数变量,

filter([1, 8, -13, 21, -9], where)    //注意where后面没有圆括号
甚至还可以是匿名函数:
filter([1, 8, -13, 21, -9], function(number){
	return number<0;}
)


Lambda表达式

  • JavaScript是弱类型语言,变量想装啥就装啥;
  • 但C#是强类型语言,一个函数算啥类型啊?所以推出了delegate;
  • Java一直揣着,不屑的翻了个白眼:旁门左道!要面向对象,要面向对象,要面向对象,知道吗?

……时光飞逝……

  • C#进化:匿名方法 => 划时代的Lamda,好评如潮
  • Java:骂骂咧咧的推出了Lambda
  • JavaScript:ES6中推出了类Lambda的“箭头函数”

本质上,Lambda就是一个匿名函数。而且省略掉了function/delegate关键字,引入了一个箭头 =>(C#和JavaScript),->(Java)

以JavaScript为例:

	var where = (number) => {
		return number > 0;
	}

然后,再引入一些简写规则:

var where = number => {  //一个参数可以不加()
var where = () => {  //但没有参数也要保留圆括号()
var where = () => alert('一行代码省略花括号');
var where = (number) => number > 0;  //连return也省略了



作用域/闭包(待修改)

根据变量的作用域规则,lambda表达式中可以使用其外部的变量
boolean quicken = true;
IMove m = (s, t) -> {
	if (quicken) {  //OK
		s *= 2;
	}			
	return s * t;
};		
System.out.println(s); //error

那么,如果:

  • 一个方法返回的是一个lambda表达式
  • 而且这个lambda表达式使用了(在方法体中的)外部变量
static IMove goSchool() {
	boolean quicken = true;
	return (s, t) -> {
		//以下代码会在何时执行?
		if (quicken) {  //OK
			s *= 2;
		}			
		return s * t;
	};	
}

这就会形成(狭义上的)闭包,即:lambda表达式延长了quicken的生命周期。

  • 如果goSchool()中没有lambda表达式,如果lamda表达式中没有使用quicken:quicken应该在goSchool()被调用结束后被销毁(出栈)
  • 但goSchool()执行完成返回的是一个(尚未执行的)函数,该函数直到再次被调用才开始真正执行
  • 所以quicken应该被保留,不能被销毁

断点演示:


事件机制

重用,只是“函数做变量”的一个作用。事件机制,才是它的必然。

为什么叫做回调?

正常调用:我要实现某个功能,需要某个函数(我知道这个函数干嘛的),于是我开开心心的去调用它。

但回调:

  • 我(filter)要调用某个函数(where(numbers[i])),但其实我不知道它(where)究竟要干嘛呢……懵+1
  • 嗯,当我(filter)被某个小可爱调用的时候,他会告诉我这个函数要干嘛的(filter(numbers, x=>x>0))……懵+2

为什么要搞得这么复杂?

事件

编程开发的事件里,比如点击一个按钮,就可以触发了一个事件(event)。类似的还有:移动鼠标、右键、按下键盘……都可以触发事件。

按钮是谁做的?以后你们就会知道:按钮是浏览器厂商做好的,我们只需要简单的声明就可以生成:

<button>我是按钮</button>
但是,浏览器厂商的开发人员在生成这个按钮的时候(一样要写代码),
  • 要预留一个功能:能够被点击(click),并且点击之后能够予以响应;
  • 但是并不知道如何响应(是弹出一个中奖窗口呢,还是地球即将毁灭的警告),如何响应是我们Web开发人员去设置的

怎么办?在线等,挺急的……

就让button暴露一个click事件(算了,我通俗点……)。大致对话内容如下:

  • 浏览器开发:你声明button的时候可以指定(绑定)click事件哟!绑定之后,用户只要一点击,我就会执行你的指示
  • Web开发:好勒!但是,我怎么“指示”你呢?

当然是函数了:

function welcome(){
    alert("一起帮·源栈课堂欢迎您!")
}

这个函数就是回调函数,因为它会被作为参数传递给button:

<button onclick="welcome()">我是按钮</button>
在button被点击时调用(注意:不是一声明就调用)


作业

  1. 声明一个匿名函数,能够求两个参数的和,然后将其赋值给变量calculate
  2. 给calculate用箭头函数重新赋值,让其能够求两个参数的差
  3. 声明一个命名函数merge,能够接受两个参数:一个数组numbers,一个回调函数calculate;
    然后merge()中调用calculate,将数组中相邻两个数进行运算(求和求差等),返回一个新数组。
    比如:传入numbers是[2,9,18,20,15,6],calculate是取和,则merge()函数返回数组为:[11,38,21]
    说明:传入的数组元素个数始终是偶数,不考虑元素奇数个数情景
  4. 将merge函数绑定到button的click事件上,点击button运行


回调 委托 Lamda
赞: 0 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0

编程基础


项目管理相关

需求发布、开发规划、部署、测试,源代码版本管理(git)等……

逸闻史话

认识计算机

编程语言

数据结构和算法

Web开发基础

全部
关键字



帮助

反馈