# 为什么要有操作符
一个操作符是返回一个
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
。