众所周知,为了避免DOM渲染的冲突,Javascript是单线程模式,该如何解决耗时操作问题?(鼠标键盘事情的处理,远程http请求以及文件io操作等等。
)——异步。
同步代码,直接执行
异步函数先放在异步队列中
待同步函数执行完毕,轮询执行异步队列函数
同步执行异步程序带来回调地狱问题
ES6中引入了Promises代表了在未来某个时刻完成。
把异步中使用回调函数的场景改为了.then()、.catch()等函数链式调用的方式。基于promise我们可以把复杂的异步回调处理方式进行模块化。
然而promise也有自身的缺点:
- 数据源产生多个值,比如鼠标移动事情或者文件系统的字节流;
- 没有失败重试的机制;
- 没有取消机制;
简介
RxJS 是一个响应式编程的库,响应式的思路是把随时间不断变化的数据、状态、事件等等转成可被观察的序列(Observable Sequence),然后订阅序列中那些Observable对象的变化,一旦变化,就会执行事先安排好的各种转换和操作,使编写异步或基于回调的代码更容易。
- Vue 的底层就是採用了 Reactive Programming 的观念来实作的,另外 Vue 官方也推出了 vue-rx
- Angular 2也全面引用了 RxJS
- Redux中加入了对 Observable 操作的支持。
区别:
Promise .then()只能返回一个值,Observable可以返回多个值
Promise要么resolve要么reject,并且只响应一次。而Observable可以响应多次
Promise不能取消,Observable可以调用unsubscribe()取消订阅
Observable认为是加强版的Promise,它们之间是可以通过RxJS的API互相转换的:
const observable = Observable.fromPromise(promise); // Promise转为Observable
const promise = observable.toPromise(); // Observable转为Promise
- 我们知道传统的for,while对循环体中的异步程序是无法感知的,或者说,它们不会等待异步程序执行完毕再进入下一轮循环。
- 错误处理是任何程序都要解决的问题,本身就已很复杂的回调函数中再嵌入try/catch块吗?如果还想加入重试机制呢?
- 商业逻辑内嵌在回调函数中,可读性差,复杂度高。现如今流行的组件化编程,即可重用,又可解耦,还方便测试;
- 闭包是强大的,过度地使用闭包将导致我们不得不谨慎地审视变量的作用域以及其值。再加上共享变量带来的副作用,混杂在if/else条件语句和for循环中,每天都会有修不完的bug;
- 根据事件或耗时操作无响应的时间进行取消操作;
- 自己实现throttling和debouncing是很困难的
- 众所周知的事件监听带来的内存泄露问题;
在RxJS中,存在这么几种东西:
- Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
- Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
- Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
- Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像 map、filter、concat、flatMap 等这样的操作符来处理集合。
- Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
- Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeout 或 requestAnimationFrame 或其他。
建立 Observable
Observable 就像是一个序列,裡面的元素会随著时间推送。
var observable = Rx.Observable
.create(observer => {
observer.next('Jerry');
observer.next('Anna');
})
// 订阅这个 observable
observable.subscribe(value => {
console.log(value);
})
观察者 Observer
// 观察者, next, error, complete 三个方法
var observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
observable.subscribe(observer)
我们可以把一切输入都当做数据流来处理
用户操作
网络响应
定时器
RxJS提供了各种API来创建数据流:
单值:of, empty, never
多值:from
定时:interval, timer
从事件创建:fromEvent
从Promise创建:fromPromise
自定义创建:create
创建出来的数据流是一种可观察的序列,可以被订阅,也可以被用来做一些转换操作,比如:
改变数据形态:map, mapTo, pluck
过滤一些值:filter, skip, first, last, take
时间轴上的操作:delay, timeout, throttle, debounce, audit, bufferTime
累加:reduce, scan
异常处理:throw, catch, retry, finally
条件执行:takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
转接:switch
也可以对若干个数据流进行组合:
concat,保持原来的序列顺序连接两个数据流
merge,合并序列
race,预设条件为其中一个数据流完成
forkJoin,预设条件为所有数据流都完成
zip,取各来源数据流最后一个值合并为对象
combineLatest,取各来源数据流最后一个值合并为数组
操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。
许多操作符都是跟时间相关的,它们可能会以不同的方式延迟(delay)、取样(sample)、节流(throttle)或去抖动值(debonce)。图表通常是更适合的工具。弹珠图是操作符运行方式的视觉表示,其中包含输入 Obserable(s) (输入可能是多个 Observable )、操作符及其参数和输出 Observable 。
rxjs应用观察者模式,其中包含2个重要的实例:Observer观察者和Subject被观察对象,多个Observer注册到Subject中,在Subject功能触发时,会通知注册好的Observab列表,逐一通知其响应观察变更信息。
很多时候,我们会有一些显示时间的场景,比如在页面下添加评论,评论列表中显示了它们分别是什么时间创建的
tick() {
this.diff = moment(createAt).fromNow()
setTimeout(tick.bind(this), 1000)
}
但组件并不一定只有一份实例,这样,整个界面上可能就有很多定时器在同时跑,这是一种浪费。如果要做优化,可以把定时器做成一种服务,把业务上需要周期执行的东西放进去,当作定时任务来跑。
Observable.interval(1000).subscribe(() => {
this.diff = moment(createAt).fromNow()
})
有很多数据,非常多关于数据的操作
展示的数据是多个数据组合而成,比如任务、对应owner、标签等
同一个数据的更新,可能来自不同的发起方
新增的数据需要的数据处理规则应该和原来的相同
解决:
数据通过缓存和异步方式获取
把每个数据流管道组合起来,流的叠合就是最后的数据
获取和订阅放在一起,也就不需要知道数据的来源是哪里了
现在和未来的数据merge之后通过相同的API处理,保证数据的规则相同
###使用RxJS实现搜索功能
- 多余的请求
- 已无用的请求仍然执行
<input id="text"></input>
<script>
var text = document.querySelector('#text'),
timer = null;
text.addEventListener('keyup', (e) =>{
// 在 250 毫秒内进行其他输入,则清除上一个定时器
clearTimeout(timer);
// 定时器,在 250 毫秒后触发
timer = setTimeout(() => {
console.log('发起请求..');
},250)
})
</script>
<input id="text"></input>
<script>
var text = document.querySelector('#text'),
timer = null,
currentSearch = '';
text.addEventListener('keyup', (e) =>{
clearTimeout(timer)
timer = setTimeout(() => {
// 声明一个当前所搜的状态变量
currentSearch = '书';
var searchText = e.target.value;
$.ajax({
url: `/search/${searchText}`,
success: data => {
// 判断后台返回的标志与我们存的当前搜索变量是否一致
if (data.search === currentSearch) {
// 渲染展示
render(data);
} else {
// ..
}
}
});
},250)
})
</script>
vue+rxjs
import Vue from 'vue'
import VueRx from 'vue-rx'
import Rx from 'rxjs/Rx'
Vue.use(VueRx, Rx)
在Vue实例当中就会多了这个钩子函数
subscriptions:用法类似data
domStreams:存放事件
<input type="text" v-stream:keyup="getInput$">
<p>value$: {{ value$ }}</p>
import { Observable } from 'rxjs';
export default {
domStreams: ['getInput$'],
subscriptions () {
return {
value$: this.getInput$
.pluck('event', 'target', 'value')
.debounceTime(2000)
.distinctUntilChanged()
.switchMap(url => Http.get(url))
.map(val => {
console.log(val);
})
}
}
}
debounceTime
只有在特定的一段时间经过后并且没有发出另一个源值,才从源 Observable 中发出一个值。
distinctUntilChanged
返回 Observable,它发出源 Observable 发出的所有与前一项不相同的项。
如果输入值没有变化,则不要发起请求(比如按某个字符,然后快速按退格)。
source : --a--b--c--c--b|
distinctUntilChanged()
example: --a--b--c-----b|
switchMap
处理高阶 Observable 就是指一个 Observable 送出的元素还是一个 Observable
var click = Rx.Observable.fromEvent(document.body, 'click');
var source = click.map(e => Rx.Observable.interval(1000));
var example = source.switch();
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.switchMap(
e => Rx.Observable.interval(100).take(3)
);
缺点
RxJS的抽象程度很高,可以用很简短代码表达很复杂的含义。