# 为什么要有操作符
一个操作符是返回一个
Observable对象的函数,不过,有的操作符是根据其他Observable对象产生返回的Observable对象,有的操作符则是利用其他类型输入产生返回的Observable对象,还有一些操作符不需要输入就可以凭空创造一个Observable对象。
所有操作符中最容易理解的可能就是 map 和 filter ,因为 JavaScript 的数组对象就有这样两个同名的函数 map 和 filter:
const source = [1, 2, 3, 4];
const result = source.filter(x => x % 2 === 0).map(x => x * 2);
console.log(result);
// [4, 8]几个关键点:
filter和map都是数组对象的成员函数filter和map的返回结果依然是数组对象filter和map不会改变原本的数组对象
因为上面的特点,filter 和 map 可以链式调用,一个复杂的功能可以分解为多个小任务来完成,每个小任务只需要关注问题的一个方面。此外,因为数组对象本身不会被修改,小任务之间的耦合度很低,这样的代码就更容易维护。
const result$ = source$.filter(x => x % 2 === 0).map(x => x * 2);
result$.subscribe(console.log);
在 RxJS 的世界中,filter 和 map 这样的函数就是操作符,每个操作符提供的只是一些通用的简单功能,但通过链式调用,这些小功能可以组合在一起,用来解决复杂的问题。
# 操作符的分类
# 功能分类
- 创建类(creation)
- 转化类(transformation)
- 过滤类(filtering)
- 合并类(combination)
- 多播类(multicasting)
- 错误处理类(error Handling)
- 条件分支类(conditional&boolean)
- 数学和合计类(mathmatical&aggregate)
- 其他
- 背压控制类(backpressure control)
- 可连接类(connectable)
- 高阶
Observable(higher order observable)处理类
# 静态和实例分类
所有的操作符都是函数,不过有的操作符是 Observable 类的静态函数,也就是不需要 Observable 实例就可以执行的函数,所以称为“静态操作符”;另一类操作符是 Observable 的实例函数,前提是要有一个创建好的 Observable 对象,这一类称为“实例操作符”。
无论是静态操作符还是实例操作符,它们都会返回一个 Observable 对象。
一个操作符应该是静态的形式还是实例的形式,完全由其功能决定。有意思的是,有些功能既可以作为 Observable 对象的静态方法,也可以作为 Observable 对象的实例方法。
# 如何实现操作符
# 操作符函数的实现
每个操作符都是一个函数,不管实现什么功能,都必须考虑下面这些功能要点:
- 返回一个全新的
Observable对象 - 对上游和下游的订阅及退订处理
- 处理异常情况
- 及时释放资源
function map (project) {
// 返回新的 Observable 对象,可以用于链式调用
return new Observable(observer => {
// this 代表上游的 Observable 对象
const sub = this.subscribe({
next: value => {
// 异常捕获,并传递给下游
try {
observer.next(project(value))
} catch (error) {
observer.error(error)
}
},
// error 和 complete 直接交给下游处理
error: error => observer.error(error),
complete: () => observer.complete()
});
// 当下游退订时,需要对上游做退订的动作
return {
unsubscribe: () => {
sub.unsubscribe();
}
};
});
}
# 关联 Observable
- 给
Observable打补丁
// 实例操作符
Observable.prototype.map = map;
如果是静态操作符,则是直接赋值给 Observable 类的某个属性。
- 使用
bind绑定特定Observable对象
const result$ = map.bind(source$)(project);即
代码语言:javascript复制const operator = map.bind(source$);
const result$ = operator(project);
- 使用
lift
RxJS v5 版本对架构有很大的调整,很多操作符都使用一个神奇的 lift 函数实现,lift 的含义就是“提升”,功能是把 Observable 对象提升一个层次,赋予更多功能。lift 是 Observable 的实例函数,它会返回一个新的 Observable 对象,通过传递给 lift 的函数参数可以赋予这个新的 Observable 对象特殊功能。
function map (project) {
return this.lift(function (source$) {
return source$.subscribe({
next: value => {
try {
this.next(project(value))
} catch (error) {
this.error(error)
}
},
error: error => this.error(error),
complete: () => this.complete()
});
});
}
Observable.prototype.map = map;
虽然 RxJS v5 的操作符都架构在 lift 上,应用层开发者并不经常使用 lift ,这个 lift 更多的是给 RxJS 库开发者使用。
# 改进的操作符定义
如果严格遵照函数式编程的思想,应该尽量使用纯函数,纯函数的执行结果应该完全由输入参数决定,如果函数中需要使用 this ,那就多了一个改变函数行为的因素,也就算不上真正的纯函数了。定义操作符的函数中访问 this ,实际上违背了面向函数式编程的原则。
- 操作符和
Observable关联的缺陷
无论是静态操作符还是实例操作符,通常在代码中只有用到了某个操作符才导入(import)对应的文件,目的就是为了减少最终的打包文件大小。
用给 Observable “打补丁”的方式导入操作符,每一个文件模块影响的都是全局唯一的那个 Observable。
- 使用
call来创建库
对于实例操作符,可以使用前面介绍过的 bind/call 方法,让一个操作符函数只对一个具体的 Observable 对象生效;对于静态操作符,就直接使用产生 Observable 对象的函数,而不是依附于 Observable 的静态函数。
静态操作符不能包含对 this 的访问,所以其实不需要和 Observable 类有任何关系,以前把它们挂在 Observable 类上,纯粹就是为了表示两者有些语义联系而已。
对于实例操作符,因为函数实现要访问 this ,所以需要用 bind 或者 call 的方式来绑定 this。


