函数式编程

什么是函数式编程,函数式编程能为我们解决什么问题?

一、定义

函数式编程 是一种 编程范式,是编程的一种方法论。其中常见的编程范式有 命令式编程函数式编程逻辑式编程

命令式编程 是对 计算机硬件的一种抽象,拥有 变量(存储单元),表达式(内存引用、数学运算)和控制语句(跳转指令)。通过将达到目的的步骤详情的描述出来,交给程序处理。即命令式程序就是一个冯诺依曼机的 指令序列

函数式编程 则是对数学的抽象,将运算过程描述为一种 表达式求值

简单来说就是,命令式编程关心解决问题的步骤,而函数式编程关心数据的映射 (该映射规则只关心输入,相同的输入总是获得一致的输出)。

举个例子对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// demo 计算 1 + 2 - 3 + 4
// 命令行式
let a = 1 + 2;
a = a - 3;
a = a + 4;

// 函数式
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
add(subtract(add(1, 2), 3), 4)

为了避免代码过于 横向拓展,陷入“回调地狱”,建议进行 链式优化Promise模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// loadash 链式写法
const utils = {
chain (n) {
this._temp = n;
return this;
},
add (b) {
this._temp += b;
return this;
},
substract (b) {
this._temp += b;
return this;
},
};
utils.chain(1).add(2).subtract(3).add(4);

1. 函数式编程的本质:

  • 函数是一等公民(函数与其他数据类型一样,处于平等地位,能进行赋值和传参)
  • 引用透明 & 没有副作用(函数的运行不依赖外部状态,也不会修改外部状态)
  • 不可变性(函数保持独立,不会对输入参数进行变更,只返回新的值)

函数式编程具有的好处有:

  • 语义更加清晰
  • 可复用性更高
  • 可维护性好(函数保持独立,没有副作用,每一个函数都可视作一个单元,进行测试和调试)
  • 易于“并发”,不会造成资源争用(不依赖外部状态)

不过由于函数参数的不可变性,纯函数编程语言是无法实现循环的,只能通过递归的方式解决迭代问题,这使得函数式编程严重依赖递归。

1
2
3
4
5
// 阶乘
function factorial (n) {
if (n === 0) return 1;
return factorial(n - 1) * n;
}

这样的递归调用有更高的开销和局限(调用栈深度),可以尽量把递归写成尾递归的方式,编译器会自动优化为循环。

二、常见函数式编程模型

1. 高阶函数(Higher-order function)

接受或返回一个函数的函数被称为高阶函数

1
2
3
4
5
6
7
8
9
10
const arr = [1, 2, 3];

// map 映射将集合的每一项都做了相同转换处理
arr.map(n => ++n);

// 等价于
const result = [];
for (const i in arr) {
result.push(++i);
}

2. 闭包(Closure)

可以保留局部变量不被释放的代码块,被称为一个闭包。闭包存在内、外两层函数,同时内层函数对外层函数的局部变量进行了引用。

例如斐波那契数列,可利用闭包缓存运算结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建闭包
function fibonacciSequence() {
const data = [1, 1];
const sequence = n => {
if (!data[n]) {
data[n] = sequence(n - 1) + sequence(n - 2);
}
return data[n];
}
return sequence;
}

const sequence = fibonacciSequence();
console.log(sequence(6)); // 13
console.log(sequence(5)); // 8

闭包的主要用于持久化变量,并利用这些变量做缓存或者持久化变量。但是持久化变量会持续占用内存空间,易造成内存浪费,一般需要进行额外的手动清除。

3. 柯里化(Currying)

给定一个函数的部分参数,生成一个接受其他参数的新函数,即为柯里化。

1
2
3
4
5
6
7
8
9
10
11
// 获取相对于 BASE 的路径
const BASE = '/path/to/base';

// 一般写法
const aPath = path.relative(BASE, '/a');
const bPath = path.relative(BASE, '/b');

// _.parical 改写
const relativeFromBase = _.partial(path.relative, BASE);
const aPath = relativeFromBase('/a');
const bPath = relativeFromBase('/b');

本例中 aPathbPath 不用再关心 BASE 路径。柯里化可以使我们只关心函数的部分参数,使函数的用途更加清晰,调用更加简单。

4. 组合(Composing)

将多个函数的能力合并,创造一个新的函数,便是组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 数组中每个单词大写,做 Base64
const arr = ['pen', 'apple', 'applypen'];

// 一般写法 (其中一种)
const rs = [];
for(const w of arr){
rs.push(btoa(w.toUpperCase()));
}
console.log(rs);

// _.flow 改写
const arr = ['pen', 'apple', 'applypen'];
const upperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa));
console.log(upperAndBase64(arr));

_.flow 将转大写和转 Base64 的函数的能力合并,生成一个新的函数。方便作为参数函数或后续复用。

5. 函数式组件

一个函数就是一个组件,其入参为渲染的上下文,返回值则是渲染好的 HTML。

对于函数式组件而言,其特点有:

  • Stateless,组件自身没有状态
  • Instanceless,组件自身也没有实例,即 this
  • 无生命周期

当组件不涉及内部状态,只是用于数据渲染时,函数式组件更轻量,性能更好。具体内容可参考 ReactVue 的函数式组件。

三、自己眼中的函数式编程

我理解的函数式编程,是 以函数为载体,进行数据的映射处理,其独立而不受外界影响。虽然函数式编程有很多优点和场景,但是在开发过程中,我们不应该拘泥于这一种编程思维,而应该多对比思考其他思想,甚至多种结合进行设置。

此处警醒自己不要钻牛角尖

四、参考文章

-------------本文结束感谢您的阅读-------------
0%