qrcode

马上订阅,开启修仙之旅

RxJS 函数式与响应式编程

什么是函数式编程

简单说,”函数式编程”是一种 “编程范式”(programming paradigm),也就是如何编写程序的方法论。

函数式编程基本要素

所谓 “一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为其它函数的返回值。

函数赋值给变量:

1
2
const greet = function(msg) { console.log(`Hello ${msg}`); }
greet('Semlinker'); // Output: 'Hello Semlinker'

函数作为参数:

1
2
3
const logger = function(msg) { console.log(`Hello ${msg}`); };
const greet = function(msg, print) { print(msg); };
greet('Semlinker', logger);

函数作为返回值:

1
2
3
4
5
6
7
8
const a = function(a) {
return function(b) {
return a + b;
};
};

const add5 = a(5);
add5(10); // Output: 15

函数式编程重要特性

  1. 只用表达式,不用语句

“表达式”(expression)是一个单纯的运算过程,总是有返回值;”语句”(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

  1. 纯函数

纯函数的特点:

  • 给定相同的输入参数,总是返回相同的结果。
  • 没有依赖外部变量的值。
  • 没有产生任何副作用。

纯函数的示例:

1
2
const double = (number) => number * 2;
double(5);

非纯函数示例:

1
2
3
Math.random(); // => 0.3384159509502669
Math.random(); // => 0.9498302571942787
Math.random(); // => 0.9860841663478281

所谓 “副作用”)(side effect),是指函数内做了与本身运算无关的事,比如修改某个全局变量的值,或发送 HTTP 请求,甚至函数体内执行 console.log 都算是副作用。函数式编程强调函数不能有副作用,也就是函数要保持纯粹,只执行相关运算并返回值,没有其他额外的行为

函数式编程的优势

  1. 代码简洁,开发快速

函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。

  1. 接近自然语言,易于理解,可读性高

函数式编程的自由度很高,可以写出很接近自然语言的代码。我们可以通过一系列的函数,封装数据的处理过程,代码会变得非常简洁且可读性高,具体参考以下示例:

1
[1,2,3,4,5].map(x => x * 2).filter(x => x > 5).reduce((p,n) => p + n);
  1. 可维护性高、方便代码管理

函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

  1. 易于 “并发编程”

函数式编程不需要考虑”死锁”(deadlock),因为它不修改变量,所以根本不存在”锁”线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署”并发编程”(concurrency)。

JavaScript 函数式编程常用方法

  1. forEach

在 ES 5 版本之前,我们只能通过 for 循环遍历数组:

1
2
3
4
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
for (var i =0, len = heroes.length; i < len; i++) {
console.log(heroes[i]);
}

在 ES 5 版本之后,我们可以使用 forEach 方法,实现上面的功能:

1
2
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
heroes.forEach(name => console.log(name));
  1. map

在 ES 5 版本之前,对于上面的示例,如果我们想给每个英雄的名字添加一个前缀,但不改变原来的数组,我们可以这样实现:

1
2
3
4
5
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
prefixedHeroes.push('Super_' + heroes[i]);
}

在 ES 5 版本之后,我们可以使用 map 方法,方便地实现上面的功能:

1
2
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = heroes.map(name => 'Super_' + name);
  1. filter

在 ES 5 版本之前,对于 heroes 数组,我们想获取名字中包含 m 字母的英雄,我们可以这样实现:

1
2
3
4
5
6
7
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
if(/m/i.test(heroes[i])) {
filterHeroes.push(heroes[i]);
}
}

在 ES 5 版本之后,我们可以使用 filter 方法,方便地实现上面的功能:

1
2
3
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterRe = /m/i;
var filterHeroes = heroes.filter(name => filterRe.test(name));

响应式编程

什么是响应式编程

响应式编程就是用异步数据流进行编程,这不是新理念。即使是最典型的点击事件也是一个异步事件流,从而可以对其进行侦测(observe)并进行相应操作。

可以基于任何东西创建数据流。流非常轻便,并且无处不在,任何东西都可以是一个流:用户输入、缓存、数据结构等等。例如,想象一下微博推文也可以是一个数据流,和点击事件一样。你可以对其进行侦听,并作相应反应。

Reactive Extension

Rx(Reactive Extension)的概念最初由微软公司实现并开源,也就是 Rx.NET,因为 Rx 带来的编程方式大大改进了异步编程模型,在 .NET 之后,众多开发者在其他平台和语言上也实现了 Rx 的类库。比如有 Java 实现的 RxJava,C++ 实现的 RxCpp,用 Python 实现的 RXPy,当然也包括我们后面要学习的 JavaScript 实现的 RxJS。

虽然 Rx 的主要目的是解决异步问题,按并不表示 Rx 不适合同步处理数据。实际上,在使用 Rx 后,我们开发者可以不用关心代码是被同步执行还是异步执行,所以处理起来会更加简单。

非响应式与响应式

说了那么多响应式的概念,我们来看一下非响应式的一个例子:

1
2
3
let a1 = 6;
let b1 = 6;
let c1 = a1 + b1;

上面的示例很简单,很明显 c1 的值为 12。但当我改变 a1 的值,比如改为 3 时,我们会发现 c1 的值并不会更新。同理,单独改变 b1 的值,c1 的值也不会更新。如果要获取新的值的话,我们就需要重新计算。

其实,在生活中也有对应的场景。比如商城购物车,当我们改变购物车的商品数量或者删除某个商品时,我们希望能自动更新订单金额,而不需要用户做任何其他操作。

而生活中响应式的另外一个常见例子就是 Excel 表格,以上面的例子为例,A1 单元格的值为 6,B1 单元格的值也为 6,C1 单元格的值为 a1 + b1。 当我们改变 A1 单元格或 B1 单元格的值时,你会发现 C1 单元格内的值会自动更新,而不需要我们手动执行更新操作,我们可以简单的理解,这就是响应式。

在前端领域,我们经常要跟异步场景打交道。比如 DOM 事件、AJAX、WebSocket、定时器等。通常情况下,异步的场景会比较复杂。不过值得庆幸地是,我们拥有 RxJS 这个利器。RxJS 擅长处理异步操作,因为它对数据采用 “Push”(相较于 “Pull” 方式),当一个数据产生的时候,会被主动地推给处理函数,这个处理函数不用关心数据是同步或者异步产生的,这样就让开发者从异步处理的境遇中解救出来。

参考资源


欢迎小伙伴们订阅前端修仙之路,及时阅读 Angular、RxJS、TypeScript 和 Node.js 最新文章。

qrcode