题外话:写这篇文章的目的主要是试图罗列出RxJs的关键点,以后根据这几个点可以迅速的回想起来。而关于RxJs的使用细节,官方文档就足够了RxJs, 我就不班门弄斧了。
RxJs 定义
RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 Observable,附属类型 (Observer、 Schedulers、 Subjects) 和受 [Array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。
ReactiveX 结合了 观察者模式、迭代器模式 和 使用集合的函数式编程,以满足以一种理想方式来管理事件序列所需要的一切。
以上是来自官方的定义,这也是我为什么从这三个方面着手来了解RxJs。
函数式编程
函数式编程的总结可以参考阮大神的文章:函数式编程初探
函数为一等公民 (First Class)
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为其它函数的返回值。
javascript闭包天然适合这种模式(Java, C#做不到函数作为参数或返回值)。
纯净性 (Purity)
不修改状态 - 利用参数保存状态
我觉得理解这句话有一个要点:在其他类型的语言中,变量往往用来保存”状态”(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。
function reverse(string) {
if(string.length == 0) {
return string;
} else {
return reverse(string.substring(1, string.length)) + string.substring(0, 1);
}
}
我认为这也是保持纯净性的一种措施。
RxJs中operator的定义和使用可以很好的体现出RxJs的函数式编程本质:
操作符是 Observable 类型上的方法,比如 .map(…)、.filter(…)、.merge(…),等等。当操作符被调用时,它们不会改变已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable 。
操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。
操作符本质上是一个纯函数 (pure function),它接收一个 Observable 作为输入,并生成一个新的 Observable 作为输出。订阅输出 Observalbe 同样会订阅输入 Observable 。
在了解RxJs使用的设计模式之前,我们先看一下它的基本用法:
const search$ = Rx.Observable.fromEvent(btn, 'click');
search$.subscribe(function(event) {
//Do something with event
})
观察者模式 Observable/Observer
说到观察者模式,在RxJs之前,前端领域应用最广泛的莫过于Dom事件监听了,我将它与RxJs Observable作一个简单的类比:
从观察者模式这个角度,RxJs和Dom监听的本质是一样的。observer本质上就是对于事件(或状态变化)响应的handler,使一组原始数据变的observable就是为数据添加监听机制。
当然RxJs涵盖的scope更广,包括多值推送,状态管理(next, complete, error)以及函数式编程写法等等。
再举一个更加有代表性的栗子:
var observable = Rx.Observable.create(function (observer) {
observer.next(1);
observer.next(2);
observer.next(3);
setTimeout(() => {
observer.next(4);
observer.complete();
}, 1000);
});
console.log('just before subscribe');
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
});
console.log('just after subscribe');
// 执行结果
//just before subscribe
//got value 1
//got value 2
//got value 3
//just after subscribe
//got value 4
//done
Rx.Observable.create时,并不会推送数据,这就是所谓惰性,当observavle发动subscribe并传入一个observer时,value 1, 2, 3被依次发射并被observer所接受,所谓多值推送。而依次推送,不就是一个有时间轴的stream(流)吗。
在RxJs中,observable/observer的实现方式,可以参考如下代码:
class Observable {
constructor(_subscribe) {
this._subscribe = _subscribe;
}
subscribe(observer) {
return this._subscribe(observer);
}
}
const simpleObservable = new Observable((observer) => {
let i = 0;
const id = setInterval(() => {
if (i < 3) {
observer.next(i++);
} else {
observer.complete();
observer.next('stop me!');
clearInterval(id);
}
}, 100);
//返回取消订阅句柄
return () => {
console.log('disposed!');
clearInterval(id);
}
});
const observer = {
next: value => console.log(`next -> ${value}`),
error: () => {},
complete: () => console.log('complete')
};
simpleObservable.subscribe(observer);
// 异步输出
// next -> 0
// next -> 1
// next -> 2
// complete
迭代器模式 Interator
迭代器(Iterator)模式。它提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
ES6中,Array、Set 等这些内置的可迭代类型,可以通过 iterator 方法来获取一个迭代对象,调用迭代对象的 next 方法将获取一个元素对象。
let iterable = [1, 2];
let iterator = iterable[Symbol.iterator]();
iterator.next(); // => { value: "1", done: false}
iterator.next(); // => { value: "2", done: false}
iterator.next(); // => { value: undefined, done: true}
在RxJs中在订阅一组可迭代原始类型时,内部是通过next方法来迭代的。
最后再题外说一点:
Observable和Promise的异同:
相同点:都是基于Push的生产者消费者模式。
不同点:
1. Promise是即时单值推送,Observable是惰性(lazy)多值推送。
2. Observable可取消订阅,而Promise不可以
注:关于生产者消费者中Pull和Push的描述,可参考以下链接:拉取 (Pull) vs. 推送 (Push)