前言
-
统计数据流中产生的所有数据个数 —— count
-
获得数据流中最大或者最小的数据 —— max 和 min
-
对数据流中所有数据进行规约操作 —— reduce
-
判断是否所有数据满足某个判定条件 —— every
-
找到第一个满足判定条件的数据 —— find 和 findIndex
-
判断一个数据流是否不包含任何数据 —— isEmpty
-
如果一个数据流为空就默认产生一个指定数据 —— defaultEmpty
一、操作符介绍
数学类操作符
数学类操作符是体现数学计算功能的⼀类操作符。
- count
- max
- min
- reduce
这些操作符必定会遍历上游Observable对象中吐出的所有数据才给下游传递数据, 也就是说,它们只有在上游完结的时候,才给下游传递唯⼀数据。
1.count 统计数据个数
count的作⽤是统计上游Observable对象吐出的所有数据个数。
const { timer } = rxjs;
const { take, map, count, concat } = rxjs.operators;
const source$ = timer(1000).pipe(
concat(timer(1000))
);
const count$ = source$.pipe(
count()
)
count$.subscribe(
res => {
console.log(res)
}
)
// 2
count只有在2秒钟之后才 产⽣这个数据2,因为timer延迟了source$的完结时间。
2.max和min 最⼤最⼩值
max和min的⽤法相同,唯⼀区别就是max是取得上游Observable吐出 所有数据的“最⼤值”,⽽min是取得“最⼩值”。
要取得“最⼤值”或者“最⼩值”,需要能够判断任意两个值的⼤⼩关 系。如果Observable吐出的数据类型为数值类型,“最⼤值”或者“最⼩值”的 定义很清楚,就是通过数值⽐较判断出最⼤的那⼀个数,但是,如果 Observable吐出的数据类型是复杂数据类型,⽐如⼀个对象,那必须指定 ⼀个⽐较这种复杂类型⼤⼩的⽅法,所以,max和min这两个操作符都可以 接受⼀个⽐较函数作为参数。
const { timer,of } = rxjs;
const { take, map, count, concat,min } = rxjs.operators;
const intialRelease$ = of(
{ name: 'A', year: 2011 },
{ name: 'B', year: 2013 },
{ name: 'C', year: 2015 }
);
const min$ = intialRelease$.pipe(
min((a, b) => a.year - b.year)
)
min$.subscribe(
res => {
console.log(res)
}
)
// {name: 'A', year: 2011}
3.reduce 规约统计
如果需要对上游Observable吐出的所有数据进⾏更加复杂的统计运 算,就该⽤reduce这个操作符了。reduce的意思是“规约”,这是⼀种⼗分强 ⼤的运算⽅式,在JavaScript中的数组也有reduce这个函数,和RxJS中的 reduce功能类似,只是处理的对象变成了Observable。
reduce的功能就是对⼀个集合中所有元素依次调⽤这个规约函数,这 个规约函数可以返回⼀个“累积”的结果,然后这个“累积”的结果会作为参 数和数据集合的下⼀个元素⼀起成为规约函数下次被调⽤的参数,如此遍 历集合中所有的元素,因为规约函数可以任意定义,所以最后得到的“累 积”结果也就完全可定制。
除了规约函数,reduce还有⼀个可选参数seed,这是规约过程中“累 计”的初始值,如果不指定seed参数,那么数据集合中的第⼀个数据就充当 初始值。
利⽤reduce来计算从1到100的正整数之和:
const { timer, of, range } = rxjs;
const { take, map, count, concat, reduce } = rxjs.operators;
const source$ = range(1, 100)
const reduced$ = source$.pipe(
reduce(
(acc, current) => acc + current,
0
)
)
reduced$.subscribe(
res => {
console.log(res)
}
)
// 5050
reduce有两个参数,第⼀个参数是规约函数,第⼆个参数是“累 积”值的初始值,规约函数的第⼀个 参数acc代表当前的“累积”,第⼆个参数current是当前吐出的元素值。
条件布尔类操作符
这⼀类操作符根据上游Observable对象的某些条件产⽣⼀个新的 Observable对象,涉及⼀个概念“判定函数”。
3.every 判断是否所有数据满足某个判定条件
every要求⼀个判定函数作为参数,上游Observable吐出的每⼀个数据 都会被这个判定函数检验,如果所有数据的判定结果都是true,那么在上 游Observable对象完结的时候,every产⽣的新Observable对象就会吐出⼀个 ⽽且是唯⼀的布尔值true;反之,只要上游吐出的数据中有⼀个数据检验 为false,那么也不⽤等到上游Observable完结,every产⽣的Observable对象 就会⽴刻吐出false。
⽆论最后吐出的是true或者false,every产⽣的Observable对象在吐出这个唯⼀结果之后⽴刻完结。
const { timer, of, range } = rxjs;
const { take, map, count, concat, every } = rxjs.operators;
const source$ = of(3, 1, 4, 1, 5, 9);
const every$ = source$.pipe(
every(x => x > 0)
)
every$.subscribe(
res => {
console.log(res)
}
)
// true
因为上游source会吐出true,然后完结。
const { interval, of, range } = rxjs;
const { take, map, count, concat, every } = rxjs.operators;
const source$ = interval(1000);
const every$ = source$.pipe(
every(x => x < 3)
)
every$.subscribe(
res => {
console.log(res)
}
)
// false
source吐出的数据不满⾜判定 条件,也就没必要再检查其他吐出的数据了,所以⽴刻吐出false并且完 结。
由此可见,通常不要对⼀个永不完结的Observable对象使⽤every这个 操作符,因为很可能产⽣的新Observable对象也是永不完结的,⽽我们使 ⽤every的语义往往是想要有⼀个明确结果,这就带来了⿇烦。
3.find和findIndex 判断是否所有数据满足某个判定条件
lodash中有很多⽅法,在 RxJS中都有对应的操作符,⽐如find和findIndex。RxJS和lodash的不同之处是,lodash处理的都是⼀个内容确定的数据集合,⽐如⼀个数组或者⼀个 对象,既然数据集合已经有了,所以对应的函数都是同步操作;对于 RxJS,数据可能随着时间的推移才产⽣,所以更适合于异步数据处理。
find和findIndex的功能都是找到上游Observable对象中满⾜判定条件的 第⼀个数据,产⽣的Observable对象在吐出数据之后会⽴刻完结,两者不 同之处是,find会吐出找到的上游数据,⽽findIndex会吐出满⾜判定条件的 数据序号。
const { interval, of, range } = rxjs;
const { take, map, count, concat, find } = rxjs.operators;
const source$ = of(3, 1, 4, 1, 5, 9);
const find$ = source$.pipe(
find(x => x % 2 === 0)
)
find$.subscribe(
res => {
console.log(res)
}
)
// 4
find$会吐出4,然后完结。
findIndex:
const { interval, of, range } = rxjs;
const { take, map, count, concat, findIndex } = rxjs.operators;
const source$ = of(3, 1, 4, 1, 5, 9);
const find$ = source$.pipe(
findIndex(x => x % 2 === 0)
)
find$.subscribe(
res => {
console.log(res)
}
)
// 2
如果上游Observable始终不完结,⽽且没有吐出满⾜判定条件 的数据,那么find和findIndex产⽣的Observable对象也就永远不会完结。
4.isEmpty 检查⼀个上游Observable对象是不是“空的”
isEmpty⽤于检查⼀个上游Observable对象是不是“空的”,所谓“空 的”Observable是指没有吐出任何数据就完结的Observable对象。
const { interval, of, range } = rxjs;
const { take, map, count, concat, isEmpty } = rxjs.operators;
const source$ = interval(1000);
const isEmpty$ = source$.pipe(
isEmpty()
)
isEmpty$.subscribe(
res => {
console.log(res)
}
)
// false
interval产⽣的Observable对象就绝对不是空的。
empty产⽣的Observable对象作为isEmpty的上游,得到的会是true:
const { interval, of, empty } = rxjs;
const { take, map, count, concat, isEmpty } = rxjs.operators;
const source$ = empty();
const isEmpty$ = source$.pipe(
isEmpty()
)
isEmpty$.subscribe(
res => {
console.log(res)
}
)
// true
isEmpty并 不会处理error,⽽是直接把这个error丢给了下游。
对于never产⽣的上游Observable对象,isEmpty将不会产⽣任何结果, 因为它的上游Observable对象既不会吐出任何数据证明它不是“空的”,也不 会完结,所以isEmpty也就会⼀直等待下去。
5.defaultEmpty 数据流为空就默认产生一个指定数据
在了解empty之后,再理解defaultIfEmpty就容易了。defaultIfEmpty做 的事情⽐empty更进⼀步,除了检测上游Observable对象是否为“空的”,还 要接受⼀个默认值(default)作为参数。
const { interval, of, empty } = rxjs;
const { take, map, count, concat, defaultIfEmpty } = rxjs.operators;
const source$ = empty();
const defaultIfEmpty$ = source$.pipe(
defaultIfEmpty('defaultIfEmpty')
)
defaultIfEmpty$.subscribe(
res => {
console.log(res)
}
)
// defaultIfEmpty
defaultIfEmpty$到底会产⽣什么样的数据,完全由上游source$决定,如果source$是⼀个空数据流,那么defaultIfEmpty$就会吐出 defaultIfEmpty 字符串作为默认 值;如果source$吐出任何⼀个数据,就证明它不是空的,defaultIfEmpty$就表现 得和source$⼀模⼀样,吐出的数据也⼀模⼀样。
小结
这些辅助类操作符在RxJS中并不处于核⼼地位,⽽且对于开发者来说又不难 实现,但是有的场景中直接使⽤这些操作符可以避免重复发明轮⼦。
参考《深入浅出RxJs》——程墨