0. 本章包含的内容

  1. 通过一个例子感受 RxJS 的编程模式
  2. 解释什么是函数式编程
  3. 解释什么是响应式编程
  4. 解释什么是Reactive Extension

1. 一个简单的RxJS例子

注:例子其实看起来很别扭,不贴代码也罢。

jQuery的例子

特点是看起来是一串指令的集合(作为写惯了指令时的程序员更适应它不是么😂),问题在于不同地方交叉访问变量,,逻辑串在一起容易引起bug。

  1. $(function() {
  2. var startTime;
  3. $('#hold-me').mousedown(function() {
  4. startTime = new Date();
  5. })
  6. $('#hold-me').mouseup(function() {
  7. if (startTime) {
  8. const elapsedMilliseconds = (new Date() - startTime);
  9. startTime = null;
  10. $('#hold-time').text(elapsedMilliseconds);
  11. $.ajax('https://timing-sense-score-board.herokuapp.com/score/' + elapsedMilliseconds).done((res) => {
  12. $('#rank').text('你超过了' + res.rank + '% 的用户');
  13. });
  14. }
  15. })
  16. })

RxJS 的例子

代码是一个个函数,没有变量,只有函数对参数做出响应然后返回结果。

  1. const holdMeButton = document.querySelector('#hold-me');
  2. const mouseDown$ = Rx.Observable.fromEvent(holdMeButton, 'mousedown');
  3. const mouseUp$ = Rx.Observable.fromEvent(holdMeButton, 'mouseup');
  4. const holdTime$ = mouseUp$.timestamp().withLatestFrom(mouseDown$.timestamp(), (mouseUpEvent, mouseDownEvent) => {
  5. return mouseUpEvent.timestamp- mouseDownEvent.timestamp;
  6. });
  7. holdTime$.subscribe(ms => {
  8. document.querySelector('#hold-time').innerText = ms;
  9. });
  10. holdTime$.flatMap(ms => Rx.Observable.ajax('https://timing-sense-score-board.herokuapp.com/score/' + ms))
  11. .map(e => e.response)
  12. .subscribe(res => {
  13. document.querySelector('#rank').innerText = '你超过了' + res.rank + '% 的用户';
  14. });

上述例子中,出现了RxJS中的第一个概念,流(Stream),也叫做数据流 或 Observable对象。

流可以用多种方式创造出来,mouseDown$ 和 mouseUp$ 都是流,holdTime$ 是通过计算衍生出来的。

mouseDown$ 代表按钮上的 mouseDown 事件集合,既代表已发生,也代表还未发生。

流中流淌的是数据,可以通过 subscribe 函数添加对数据进行操作。

2. 函数式编程

什么是函数式编程?

函数式编程对函数的使用需要满足的三个要求:

  1. 声明式
  2. 纯函数
  3. 数据不可变性

JavaScript是函数式编程语言吗?

JavaScript 不像 Lisp 那样强制要求代码必须遵循,但是不妨碍我们应用它的思想。通过应用它的编程思想,借助一点工具的帮助,来提高代码质量。RxJS就是辅助我们写出函数式代码的一种工具。

命令式 vs 声明式

使用for循环,将一个number数组每个元素的值乘以2,或者每个元素加上1,这种写法是命令式的。

  1. function addOne(arr) {
  2. const results = []
  3. for (let i = 0; i < arr.length; i++){
  4. results.push(arr[i] + 1)
  5. }
  6. return results
  7. }
  8. console.log(addOne([1, 2, 3]));

使用map函数,返回返回 value => value 2,这种写法是声明式,得益于JavaScript函数可以作为参数,可以在JavaScript中实现最简单的*声明式编程样例。

  1. function addOne(arr) {
  2. return arr.map(item => item + 1);
  3. }
  4. console.log(addOne([1, 2, 3]));

通俗地理解:命令式是在“做”一些操作,而声明式是“表达”一个结果。

纯函数

满足如下两个条件即可称为纯函数:

  1. 函数的执行过程完全由输入的参数决定
  2. 函数不会修改任何外部状态,比如全局变量、传入参数

表面上,纯函数会限制我们的功能编写,实际上它让我们的代码更简单,更加容易维护而不容易产生bug。

一个函数不纯,可能做了下面这些事:

  1. 改变全局变量的值
  2. 改变输入参数引用的对象
  3. 读取用户输入
  4. 抛出一个异常
  5. 网络输入(比如通过ajax调用api)输出
  6. 操作浏览器DOM

一个简单的判定方法就是,将函数调用替换为返回结果,看程序执行是否一样:

  1. const originalArray = [1, 2, 3];
  2. // 不纯
  3. function arrayPush(array, item) {
  4. array.push(item);
  5. return array;
  6. }
  7. const pushedArray = arrayPush(originalArray, 4);
  8. const pushedArray = [1,2,3,4]; // 显然和上一行代码程序执行不一样,上一行修改了参数
  9. //
  10. function arrayPush(array, item) {
  11. return [...array, item]
  12. }
  13. const pushedArray = arrayPush(originalArray, 4);
  14. const pushedArray = [1,2,3,4]; // 和上一行代码程序执行是一样的

纯函数是非常容易进行单元测试的,测试驱动开发这么多年没有得到全面实施,很重要的一个原因就是不遵守函数式编程规范。

数据不可变

不可变的数据就是Immutable数据,一旦产生,就可以肯定它的值永远都不会变,这非常有利于代码的理解。
当我们需要数据状态发生改变时,通过产生新的数据来实现数据的变化,而不是修改现有的数据。

为什么函数式编程最近才崛起?

计算机发展早期,数学家提出的编程模型,最优雅和易于管理,他们使用纯函数的组合来描述计算过程。最早只有Haskell和LISP这样的纯函式编程语言,因为涉及大量的数学概念和推演,学习它们需要极大的耐心。

但是当时的硬件技术还不够发达,运算和存储都又慢又贵。于是物流学家、电子电气工程师提出的命令式编程成为了编程语言的主流方向。

随着电子技术的发展,运算和存储能力早已不是问题,摩尔定律提高了电子元器件的数目,多核CPU通过分布计算进一步克服了摩尔定律到瓶颈后的性能提升问题,而使用命令式编程来协调多核或分布式任务非常困难,因为应用运行依靠运行速度和网络传输等很多因素。

使用命令式和函数式编程的运行性能差别已经非常小,而开发者更关注的是开发速度,因此原来属于命令式阵营的编程语言也开始添加函数式特性,包括JavaScript。

函数式编程和面向对象编程的比较

两种编程方式都让代码更容易理解,面向对象的方法是把状态的改变封装起来,一次达到代码清晰的目的;而函数式编程则是尽量减少变化的部分,一次让代码逻辑更清晰。

面向对象编程通过把数据封装在类的实例中,操作对象只能通过类实例的方法来读取和修改,对于毫无节制地修改数据,这无异是巨大的进步。

函数式编程则倾向于数据就是数据,函数就是函数,函数可以处理数据但是并不会像面向对象那样把它们封装在一起,而是让每个函数都不去修改原有数据(不可变性),而是产生新的数据来作为运算结果(纯函数)。

3. 响应式编程

响应式编程(Reactive Programming)是一种令人振奋的编程方式。例如,Excel中在C9的单元格上设置公式=SUM(C2,C8),那么在C2到C8度单元格发生变化了,C9的数据也会发生变化,这就是响应式的概念。

响应式编程框架 - Reactive Extension

Reactive Extension 也叫ReactiveX,简称Rx,是实践响应式编程的一套工具,大大改进了异步编程模型,是知名度最高的框架。

它最早由微软公司实现并开源,也即是Rx.NET,许多开发着在其他平台和语言上也实现了Rx类库,因此Rx是一个大家族。各种语言中都引入对应语言版本的Reactive Extension。

本书中介绍的RxJS也就是Rx的JavaScript语言实现。任何可以用JavaScript来写的应用最终都会用JavaScript写出来,JavaScript开发者可以使用JavaScript来实现各种应用之外,还有机会使用RxJS这种带有纯正计算机科学精神的开发者利器。

Rx但诞生项目虽然主要目标是解决异步处理问题,但是并不代表Rx不适合同步数据处理。事实上RxJS之后,大部分代码不需要关心自己是同步执行还是异步执行,所以处理起来会更简单。

4. RxJS是否是函数响应式编程

函数响应式编程(Functional Reactive Programming)简称为FRP,在20世纪就作为一种编程技术被严格进行了数学定义,除了Functional和Reactive之外,按照它 的发明人的描述还需要具有:denotative、temporally continuous两个元素。因此很多程序员真正接触Functional和Reactive是从Rx开始,但是因为名字被占用,哪怕Rx这样一个影响力巨大的工具,也不能称呼为FRP。但在开发者心目中,Rx就是FRP。

5. 函数响应式编程的优势

RxJS的模型被证实很成功是因为具备下面这些特点:

  • 数据流抽象了很多现实问题
  • 擅长异步处理操作
  • 把复杂问题分成简单问题的组合(简单问题通过函数解决,函数正是本书花大量篇幅介绍的操作符)