(四)温故知新系列之RXJS——RXJS操作符基础(辅助类)

150 阅读3分钟

前言

  • 统计数据流中产生的所有数据个数 —— 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

image.png

count产⽣的数据就是2,值得注意的是count产⽣的数据就是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吐出的每⼀个数都是⼤于0的,所以全部能通过every的判定函数检验,最后every吐出的每⼀个数都是⼤于0的,所以全部能通过every 的判定函数检验,最后every会吐出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实际上是⼀个永不完结的数据流,每⼀秒钟吐出⼀个递增的整数,在吐出3的时候,every就会发现source实际上是⼀个永不完结的数据流,每⼀秒钟吐出⼀个递 增的整数,在吐出3的时候,every就会发现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》——程墨