03-解锁反应式编程:理解函数式编程

255 阅读6分钟

在正式进入RxJS的世界之前,我们有必要先系统的了解一下函数式编程。当然,如果你是在这方面经验丰富的开发者,完全可以跳过本文,直接开始下一章节。

参考资料

  1. Functional Programming in JavaScript

正如我们在之前描述的那样,函数式编程(Functional Programming)是一种编程范式(Programming Paradigm),一如我们熟悉的OOP,是一种组织程序代码的方法论;本质上每一种编程范式没有好与坏,只是我们认识和理解逻辑与规律的一种思维模型。

函数式编程为开发人员提供了将常见集合操作抽象为可重用、可组合的构建块的工具。

简单的理解函数式编程就是一种以函数(function)为中心的思考方式,一切从函数的角度出发去思考和理解问题。举个栗子:

1 + 1 = 2

当我们从函数式的角度去理解上述表达式,我们可以将他抽象为:

函数 加(数据1, 数据1)返回 2

这就是函数式编程的思维模型,首先,提取整个逻辑中的操作(函数),也可以是数个原子操作的组合,之后将数据作用于函数之上得到想要的结果。

Functional Programming 核心理念

并非所有的编程语言都可以践行FP,要能支持FP的必要条件之一便是函数在编程语言中可以作为一等公民。

Function as First Class

我们要怎么去理解函数作为一等公民的特性,至少要能在编程语言中满足以下几个要点:

  1. 函数能直接作为变量使用
  2. 函数能作为其他函数的参数传递
  3. 函数能作为其他函数的返回值

Functional Programming 主要特性

以表达式代替语句为主要形式

函数式编程风格下,大部分程序代码以表达式的形式而存在,取代了其他OO风格下以语句为主的形式。

// 函数调用表达式
func(1, 2);

// 赋值语句
var a = 1;

更多详细的细节可以参考下面这篇文章的内容:

Expressions versus statements in JavaScript

纯函数

纯函数是函数式编程风格中最为重要的特性之一,他要求同一个function在任何情况下的多次调用,如果传递了相同的参数,总是得到相同的返回值,并且没有任何显著的副作用

副作用

副作用是在函数式编程的世界里特有的一种表述,它代表任意的function在自身的运算之外的其他行为,比如修改了某个全局变量的值,或者更新了DOM等外部对象。

函数式编程强调函数应该尽量没有副作用,也就是所有的函数尽可能是纯函数

引用透明

正如前面强调的,纯函数在任何环境下,通过传入相同的参数,总是返回相同的执行结果。这种不依赖任何外部状态,只和传入的参数有关的特性也称之为引用透明

用参数保存状态

所有的程序都是由数据和行为两部分构成。稍有经验的开发者很容易意识到,函数式编程的风格相比OO而言,缺失了对数据模式定义。对于全局的数据(状态),我们可以存储在函数外部,然而作为程序逻辑的重要组成部分,循环结构中,函数存在运行时局部变量。

var globalState = new Array(10);

function doSomething() {

}

// 这里的变量i就是一个局部循环变量,对于多次调用相同的函数,需要额外存储该变量的值

for (var i = 0; i < 10; i ++) {
  doSomething(globalState[i]);
}

对于上述典型的场景,函数式编程风格中同样有推荐的解法,那就是通过增加额外的函数参数来传递局部状态。

function doSomething(globalState, index) {}

loop(10, function(i) {
  doSomething(globalState, i);
})

这里,通过为doSomeThing函数增加额外的索引参数,避免了创建临时的全局索引变量,这也就是利用参数保存状态

Functional Programming 的优势

高可读性

通过函数式风格来封装的程序逻辑过程,代码简洁并且具有自解释的特征,对于开发者而言,易读易理解。

高可维护性

由于函数式编程提倡的纯函数特性,大量的函数并不依赖外部的状态,这让程序的逻辑十分内聚,因而更容易进行局部的重构及测试。

易于并行处理

同样由于函数式编程的纯函数特征,很容易将程序改造为并行(Concurrency/Parallel)执行,而不用担心由于额外的副作用而造成死锁、竞态条件等并发陷阱。

Functional Programming 的魔法

函数式编程相比传统的OO编程风格具有如此多的优势,这是否意味着我们需要投入大量的精力来学习各种新的概念和方法呢? 这里让人惊奇来了,所有你能想到的针对集合/数组类型的函数操作需求,都可以由以下5个原子函数通过组合的方式得到,你只需要完全掌握这5个原子函数,你会发现在函数式的世界里你将无所不能。

  1. map
  2. filter
  3. concatAll
  4. reduce
  5. zip

当然,所有以上的原子函数都构筑在数组/集合类型可遍历/迭代的基础之上,也就是 forEach 迭代器是原子函数的原子。同时,前2个函数map与filter是其他3个原子函数实现的基础,我们可以借助于map、filter函数结合forEach来自主实现后面的3个原子函数。

这里,强烈建议有兴趣的读者访问本文开篇部分的引用地址,并亲手逐个去完成该网站提供的练习,对于理解和掌握函数式编程中的思想十分有价值。

至此,我们已经理解了函数式编程的思想、方法和核心内容,是时候踏入RxJS的世界去了解更多的内容了。

如果你觉得本文对你有些许启发,请持续关注我的公众号“戈伊星球”吧!