0. 本章包含的内容
- 通过一个例子感受 RxJS 的编程模式
- 解释什么是函数式编程
- 解释什么是响应式编程
- 解释什么是Reactive Extension
1. 一个简单的RxJS例子
注:例子其实看起来很别扭,不贴代码也罢。
jQuery的例子
特点是看起来是一串指令的集合(作为写惯了指令时的程序员更适应它不是么😂),问题在于不同地方交叉访问变量,,逻辑串在一起容易引起bug。
$(function() {
var startTime;
$('#hold-me').mousedown(function() {
startTime = new Date();
})
$('#hold-me').mouseup(function() {
if (startTime) {
const elapsedMilliseconds = (new Date() - startTime);
startTime = null;
$('#hold-time').text(elapsedMilliseconds);
$.ajax('https://timing-sense-score-board.herokuapp.com/score/' + elapsedMilliseconds).done((res) => {
$('#rank').text('你超过了' + res.rank + '% 的用户');
});
}
})
})
RxJS 的例子
代码是一个个函数,没有变量,只有函数对参数做出响应然后返回结果。
const holdMeButton = document.querySelector('#hold-me');
const mouseDown$ = Rx.Observable.fromEvent(holdMeButton, 'mousedown');
const mouseUp$ = Rx.Observable.fromEvent(holdMeButton, 'mouseup');
const holdTime$ = mouseUp$.timestamp().withLatestFrom(mouseDown$.timestamp(), (mouseUpEvent, mouseDownEvent) => {
return mouseUpEvent.timestamp- mouseDownEvent.timestamp;
});
holdTime$.subscribe(ms => {
document.querySelector('#hold-time').innerText = ms;
});
holdTime$.flatMap(ms => Rx.Observable.ajax('https://timing-sense-score-board.herokuapp.com/score/' + ms))
.map(e => e.response)
.subscribe(res => {
document.querySelector('#rank').innerText = '你超过了' + res.rank + '% 的用户';
});
上述例子中,出现了RxJS中的第一个概念,流(Stream),也叫做数据流 或 Observable对象。
流可以用多种方式创造出来,mouseDown$ 和 mouseUp$ 都是流,holdTime$ 是通过计算衍生出来的。
mouseDown$ 代表按钮上的 mouseDown 事件集合,既代表已发生,也代表还未发生。
流中流淌的是数据,可以通过 subscribe 函数添加对数据进行操作。
2. 函数式编程
什么是函数式编程?
函数式编程对函数的使用需要满足的三个要求:
- 声明式
- 纯函数
- 数据不可变性
JavaScript是函数式编程语言吗?
JavaScript 不像 Lisp 那样强制要求代码必须遵循,但是不妨碍我们应用它的思想。通过应用它的编程思想,借助一点工具的帮助,来提高代码质量。RxJS就是辅助我们写出函数式代码的一种工具。
命令式 vs 声明式
使用for循环,将一个number数组每个元素的值乘以2,或者每个元素加上1,这种写法是命令式的。
function addOne(arr) {
const results = []
for (let i = 0; i < arr.length; i++){
results.push(arr[i] + 1)
}
return results
}
console.log(addOne([1, 2, 3]));
使用map函数,返回返回 value => value 2,这种写法是声明式,得益于JavaScript函数可以作为参数,可以在JavaScript中实现最简单的*声明式编程样例。
function addOne(arr) {
return arr.map(item => item + 1);
}
console.log(addOne([1, 2, 3]));
通俗地理解:命令式是在“做”一些操作,而声明式是“表达”一个结果。
纯函数
满足如下两个条件即可称为纯函数:
- 函数的执行过程完全由输入的参数决定
- 函数不会修改任何外部状态,比如全局变量、传入参数
表面上,纯函数会限制我们的功能编写,实际上它让我们的代码更简单,更加容易维护而不容易产生bug。
一个函数不纯,可能做了下面这些事:
- 改变全局变量的值
- 改变输入参数引用的对象
- 读取用户输入
- 抛出一个异常
- 网络输入(比如通过ajax调用api)输出
- 操作浏览器DOM
一个简单的判定方法就是,将函数调用替换为返回结果,看程序执行是否一样:
const originalArray = [1, 2, 3];
// 不纯
function arrayPush(array, item) {
array.push(item);
return array;
}
const pushedArray = arrayPush(originalArray, 4);
const pushedArray = [1,2,3,4]; // 显然和上一行代码程序执行不一样,上一行修改了参数
// 纯
function arrayPush(array, item) {
return [...array, item]
}
const pushedArray = arrayPush(originalArray, 4);
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的模型被证实很成功是因为具备下面这些特点:
- 数据流抽象了很多现实问题
- 擅长异步处理操作
- 把复杂问题分成简单问题的组合(简单问题通过函数解决,函数正是本书花大量篇幅介绍的操作符)