从RxJs到函数式编程, 观察者模式,迭代器模式

题外话:写这篇文章的目的主要是试图罗列出RxJs的关键点,以后根据这几个点可以迅速的回想起来。而关于RxJs的使用细节,官方文档就足够了RxJs, 我就不班门弄斧了。

RxJs 定义

RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 Observable,附属类型 (Observer、 Schedulers、 Subjects) 和受 [Array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。

ReactiveX 结合了 观察者模式迭代器模式使用集合的函数式编程,以满足以一种理想方式来管理事件序列所需要的一切。

以上是来自官方的定义,这也是我为什么从这三个方面着手来了解RxJs。

函数式编程

函数式编程的总结可以参考阮大神的文章:函数式编程初探

  • 函数为一等公民 (First Class)

    函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为其它函数的返回值。

    javascript闭包天然适合这种模式(Java, C#做不到函数作为参数或返回值)

  • 纯净性 (Purity)

    1. 给定相同的输入参数,总是返回相同的结果 (没有Math.random()这种)
    2. 没有产生任何副作用
    3. 没有依赖外部变量的值
  • 不修改状态 - 利用参数保存状态
    我觉得理解这句话有一个要点:在其他类型的语言中,变量往往用来保存”状态”(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到函数式编程, 观察者模式,迭代器模式_第1张图片

从观察者模式这个角度,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(流)吗。

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方法来迭代的。

最后再题外说一点:

ObservablePromise的异同:

相同点:都是基于Push的生产者消费者模式。
不同点
1. Promise是即时单值推送,Observable是惰性(lazy)多值推送。
2. Observable可取消订阅,而Promise不可以

注:关于生产者消费者中Pull和Push的描述,可参考以下链接:拉取 (Pull) vs. 推送 (Push)

你可能感兴趣的:(RxJs)