本文默认你已经十分清晰地了解语句和表达式,且能很明确地将之区分。
你也可以先去学习笔者的探索语句和表达式的本质一文。
函数的定义
函数式编程属于声明式编程的一种,我们先来了解一下什么是函数?
函数一词的含义
函数这一词的翻译最早可以追溯到清朝数学家李善兰的《代数学》:
凡此变数中函彼变数者,则此为彼之函数。
这句话的意思即是说:如果一个数的变化中包含了另一个数的变化,那么这个数是另一个数的函数。
“函”字,有包含、容纳的意思。变数也就是变量,表示一个数的值是可以变化的。
谷歌搜索给出了对函数一种理解:
有两个变量 x 和 y ,当 x 取其变化范围中的每一个特定值时,相应地有唯一的 y 与它对应,则称 y 是 x 的函数。记为 y = f ( x ),其中 x 为自变量, y 为因变量。
只根据自己的值变化的变量叫做自变量;因为其它变量的值变化导致自己的值也随着变化的变量叫做因变量。
这种理解中,y的变化中包含了x的变化,y随x而改变,因此y是x的函数。
程序中的函数
在实际的开发过程中,严格遵循函数规范的函数被称为纯函数,而程序中的函数在不同语言中可能有不同定义。以JavaScript
中的函数举例说明,其描述如下:
一个函数是一组执行任务或计算值的语句。一个
JavaScript
函数用function
关键字定义,后面跟着函数名和圆括号。
一个函数的定义包含函数名称、函数参数、语句,比如用JS定义一个简单的函数:
function square(number) {
return number * number
}
如上所示,一个完整的函数定义包括:
- 函数定义语句—— function
- 函数名称—— square
- 函数参数括号—— ()
- 部分情况下括号可以省略
- 函数参数—— number
- 参数可以为空
- 函数代码块—— {}
- 部分情况下代码块可以省略
- 函数返回值语句—— return
- 默认包含return
为什么有些定义可有可无?
这得说说JS
的箭头函数表达式,它简直将函数的定义精简到了极致:
number => number * number
这样的精简何其大胆!只有一个变量一个语句时,它不用写函数定义语句、名称、括号、花括号、返回值。
因为它没有函数名,所以称之为匿名函数。若为它赋予函数名square
,就等同于:
function square(number) {
return number * number
}
箭头函数表达式默认会将语句的运行结果当做值返回,它即是有返回值的函数,也是表达式;是函数就说明它可以用JS
的自执行函数执行,是表达式就说明它可以赋值:
// 箭头函数表达式赋值
const square = number => number * number
// 用自执行函数执行
(number => number * number)(4)
// 输出结果:16
方法和函数的区别
java中函数的概念在jdk8中引入,将java中的方法认作函数是不妥的。方法一词的含义是:“为获得某种东西或达到某种目的而采取的手段与行为方式”。ORACLE文档给出的描述是:“一种方法包含可以被调用的可执行代码。 方法是继承的,并且在非反射代码行为中,编译器会强制执行诸如重载,重写和隐藏之类的行为”,方法具有浓厚的面向对象特征。方法(method)和函数(function)都是子程序的一种,但并不相同。
函数式编程(Functional programming)
一等公民
在函数式语言中,函数作为第“一等公民”与其他数据类型处于平等地位,所以:
- 函数可以赋值给变量
- 函数可以作为参数传入另一个函数
- 函数可以作为其它函数的返回值。
代码示例:
function human(type) {
// 将函数当做返回值返回
return function() {
// 运行参数传入的函数
return type()
}
}
// 赋值给变量
let humanText = function() {
return '人类'
}
// 函数当方法参数传入
let humanType = human(humanText)
// 运行返回的函数
console.log(humanType())
闭包
维基百科中给出了对闭包的定义:
闭包(Closure)是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
在当前函数外被定义的变量,且该变量被函数内代码所使用时,可以称之为“被引用的自由变量”。
闭包环境包含了这个闭包创建时所能访问的所有局部变量与全局变量。
引用MDN中的例子进行说明:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
如上所示,即便是“displayName
函数”在“makeFunc
函数”之后运行,在运行时也能取得“局部变量name
”的值。
其它特点
之所以将函数式编程剩下的特点合在一起,是因为“第一公民”和“闭包”是语言已经实现了的,在语法层次上的约束;而其它的,则是编程者的自我约束。虽然这些约束会使代码变得更优雅,但它们绝不是实际编程中的最优解。
函数式编程的其它特点如下:
- 引用透明
- 不修改状态
- 只用"表达式",不用"语句"
- 无副作用
引用透明
引用透明是指一个函数只依赖于输入的参数,绝不使用除输入参数以外的外部变量。
不修改状态
不修改状态简单来说就是不修改变量的值,不通过变量去修改所引用的值的内容。
这里的“状态”是指一个数据变化过程中所产生的“状态”,在程序中我们用变量来存储数据每一次变化时数据的状态。举个例子:
var a = 1
a = a + 1 // 此时a的值变成2
这里的a
变量指向的数据变化过程中总共产生了两个状态“1”和“2”。
而不修改状态,即代表着不修改变量值,这有点类似于常量的定义。更改一下示例:
const a = 1
const b = a + 1
在这个示例中将a
声明为不可更改的常量,也就是a
的引用不可更改,且a
指向的数据1
作为基本类型也不可更改,数据的变化会产生新的常量b
。这里就没有修改状态了。
而在一个函数中,函数的参数值是可以被修改的,若当前函数不遵循“引用透明”规范,函数还能修改某些外部变量的状态。举个函数改变状态的例子:
let a = 1, d = []
function b(c) {
a = 2
c[0] = 3
}
b(d)
在这个示例中,函数b
修改了外部变量a
的值;并且通过函数参数c
得到的引用,去修改了数组对象d
内部的值。这个函数修改了状态。
下面举个函数不修改状态的例子:
function a(b, c) {
const d = b + 1
const e = [ (c[0] + d) ]
return e
}
const f = [1]
const g = a(2,f)
这个函数中,没有修改参数b
的值,而是产生了一个新值d
。也没有通过函数参数c
去改变数组f
的值,而是产生了一个新的数组,并将之返回。
只用表达式不用语句
其实严格来说是:“只用表达式,不用语句;但不包括除函数的return
语句,以及能够组成表达式的语句”。我认为,这条规范相当于要把函数写成纯函数了。
在上面的某个示例中包含了很多语句:
function a(b, c) {
const d = b + 1
const e = [ (c[0] + d) ]
return e
}
这里面有常量定义语句const
,以及赋值语句=
。
优化后的代码:
function a(b, c) {
return [ (c[0] + (b + 1)) ]
}
但语句可以作为组成表达式的部分出现:
function a() {
return b => b + 1
}
这里面包含了箭头函数的定义语句,但箭头函数式一个表达式,因此没有违反规范。
function a() {
return function(b) { b + 1 }
}
上面示例中,因为函数是一等公民,因此函数也可以作为表达式出现。虽然包含了语句,但因为语句作为表达式的一部分而存在。因此也没有违反规范。
结尾唠嗑
虽然我的博客只是用于记录我的学历成长经历,但平台给的浏览量让人心塞塞的。我github
上的私人博客里有很多文章,公开在博客平台的文章只是一部分。那些论调不严谨的文章不敢发出来误人子弟。所以我更新是看啥时候心血来潮了,就重新阅读一下文章,然后修修改改一下;到最后觉得可以更新了,才来更新的。
诸位看官若是看到此处,就赏个赞鼓励一下呗。