RxJS 函数式与响应式编程

2019-11-05 16:19:33 浏览数 (1)

什么是函数式编程

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

函数式编程基本要素

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

函数赋值给变量:

代码语言:javascript复制
const greet = function(msg) { console.log(`Hello ${msg}`); }
greet('Semlinker'); // Output: 'Hello Semlinker'

函数作为参数:

代码语言:javascript复制
const logger = function(msg) { console.log(`Hello ${msg}`); };
const greet = function(msg, print) { print(msg); };
greet('Semlinker', logger);

函数作为返回值:

代码语言:javascript复制
const a = function(a) {
  return function(b) {
    return a   b;
  };
};

const add5 = a(5);
add5(10); // Output: 15
函数式编程重要特性
  • 只用表达式,不用语句

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

  • 纯函数

纯函数的特点:

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

纯函数的示例:

代码语言:javascript复制
const double = (number) => number * 2;
double(5);

非纯函数示例:

代码语言:javascript复制
Math.random(); // => 0.3384159509502669
Math.random(); // => 0.9498302571942787
Math.random(); // => 0.9860841663478281

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

函数式编程的优势
  • 代码简洁,开发快速

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

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

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

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

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

  • 易于 “并发编程”

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

JavaScript 函数式编程常用方法

  • forEach

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

代码语言:javascript复制
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
for (var i =0, len = heroes.length; i < len; i  ) {
  console.log(heroes[i]);
}

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

代码语言:javascript复制
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
heroes.forEach(name => console.log(name));
  • map

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

代码语言:javascript复制
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 方法,方便地实现上面的功能:

代码语言:javascript复制
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = heroes.map(name => 'Super_'   name);
  • filter

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

代码语言:javascript复制
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 方法,方便地实现上面的功能:

代码语言:javascript复制
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 后,我们开发者可以不用关心代码是被同步执行还是异步执行,所以处理起来会更加简单。

非响应式与响应式

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

代码语言:javascript复制
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” 方式),当一个数据产生的时候,会被主动地推给处理函数,这个处理函数不用关心数据是同步或者异步产生的,这样就让开发者从异步处理的境遇中解救出来。

参考资源

  • 响应式编程入门
  • 深入浅出 RxJS

0 人点赞