从RxJs到函数式编程,观察者模式,迭代器模式
从rxjs到函数式,观察者模式,迭代器模式。
rxjs 定义
rxjs 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 observable,附属类型 (observer、 schedulers、 subjects) 和受 [array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。
reactivex 结合了 观察者模式、迭代器模式 和 使用集合的函数式编程,以满足以一种理想方式来管理事件序列所需要的一切。
以上是来自官方的定义,这也是我为什么从这三个方面着手来了解rxjs。
函数式编程
函数式编程的总结可以参考阮大神的文章:函数式编程初探
函数为一等公民 (first class)
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为其它函数的返回值。
javascript闭包天然适合这种模式(java, c#做不到函数作为参数或返回值)。
纯净性 (purity)
给定相同的输入参数,总是返回相同的结果 (没有math.random()这种) 没有产生任何副作用 没有依赖外部变量的值不修改状态 - 利用参数保存状态
我觉得理解这句话有一个要点:在其他类型的语言中,变量往往用来保存”状态”(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)