
一个操作符是返回一个
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 这样的函数就是操作符,每个操作符提供的只是一些通用的简单功能,但通过链式调用,这些小功能可以组合在一起,用来解决复杂的问题。
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.prototype.map = map;
如果是静态操作符,则是直接赋值给 Observable 类的某个属性。
bind 绑定特定 Observable 对象const result$ = map.bind(source$)(project);即
const operator = map.bind(source$);
const result$ = operator(project);
liftRxJS 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。