对于开发者来说,rxjs
中最常接触的 api
莫过于 operator
了,按照功能的不同,目前版本的 rxjs
内置的 operator
分为十类:Creation
、Join Creation
、Transformation
、Filtering
、Join
、Multicasting
、Error Handling
、Utility
、Conditional and Boolean
、Mathematical and Aggregate
,在数量上则超过 100
个,能够满足几乎所有的场景,如果碰到无法很好适应的场景,也可以自定义一个属于自己的 operator
限于篇幅原因,所以不可能把这一百多个 operator
源码全部过一遍,也没必要,因为很多逻辑都共用的,我会从这十类 operator
中,每类挑选几个作为范例进行源码解读
Creation
from
from
能够从 Array
、array-like
、Promise
、iterable
、Observable-like
、readable-stream-like
这几类对象上创造出 Observable
(即将这几类对象作为数据源),这几类对象几乎可以囊括所有的 js
对象了,所以 rxjs
给 from
的注释是:Converts almost anything to an Observable
例如,从 Array
:
import { from } from 'rxjs';
const array = [10, 20, 30];
const result = from(array);
result.subscribe(x => console.log(x));
// Logs:
// 10
// 20
// 30
复制代码
从 iterable
:
import { from } from 'rxjs';
import { take } from 'rxjs/operators';
function* generateDoubles(seed) {
let i = seed;
while (true) {
yield i;
i = 2 * i;
}
}
const iterator = generateDoubles(3);
const result = from(iterator).pipe(take(10));
result.subscribe(x => console.log(x));
// Logs:
// 3
// 6
// 12
// 24
// 48
// 96
// 192
// 384
// 768
// 1536
复制代码
既然能处理各种对象,那么其内部肯定是有对应的条件分支来做判断的,然后根据不同的对象类型,进入不同的处理方法,最终都生成一个 Observable
// node_modules/rxjs/src/internal/observable/from.ts
export function from<T>(input: ObservableInput<T>, scheduler?: SchedulerLike): Observable<T> {
return scheduler ? scheduled(input, scheduler) : innerFrom(input);
}
复制代码
from
接收两个函数,第一个是数据源,第二个是 scheduler
,之前文章已经说过这个东西了,就是让开发者有能力调度数据的流动逻辑,一般情况下,都是只传入第一个参数,那么就会执行 innerFrom
方法
// node_modules/rxjs/src/internal/observable/innerFrom.ts
export function innerFrom<T>(input: ObservableInput<T>): Observable<T> {
if (input instanceof Observable) {
return input;
}
if (input != null) {
if (isInteropObservable(input)) {
return fromInteropObservable(input);
}
if (isArrayLike(input)) {
return fromArrayLike(input);
}
if (isPromise(input)) {
return fromPromise(input);
}
if (isAsyncIterable(input)) {
return fromAsyncIterable(input);
}
if (isIterable(input)) {
return fromIterable(input);
}
if (isReadableStreamLike(input)) {
return fromReadableStreamLike(input);
}
}
throw createInvalidObservableTypeError(input);
}
复制代码
innerFrom
方法用于将输入的对象 input
转为 observable
。其根据传入的对象类型,选择不同的处理逻辑的方法,isXXX
是用于判定类型的方法,fromXXX
则是将不同的对象转为 observable
的方法,例如对于 fromPromise
export function fromPromise<T>(promise: PromiseLike<T>) {
return new Observable((subscriber: Subscriber<T>) => {
promise
.then(
(value) => {
if (!subscriber.closed) {
subscriber.next(value);
subscriber.complete();
}
},
(err: any) => subscriber.error(err)
)
.then(null, reportUnhandledError);
});
}
复制代码
内部就是new Observable
,然后再将这个实例返回
fromEvent
fromEvent
的类型签名还是比较多的,但总体来说就是四个参数
target
target
的合法类型有三种: HasEventTargetAddRemove
、NodeStyleEventEmitter
、JQueryStyleEventEmitter
export interface HasEventTargetAddRemove<E> {
addEventListener(
type: string,
listener: ((evt: E) => void) | EventListenerObject<E> | null,
options?: boolean | AddEventListenerOptions
): void;
removeEventListener(
type: string,
listener: ((evt: E) => void) | EventListenerObject<E> | null,
options?: EventListenerOptions | boolean
): void;
}
复制代码
对于 HasEventTargetAddRemove
,很明显,是针对 HTMLElement
的,所以可以传入 dom
元素,类似的,NodeStyleEventEmitter
允许传入 nodejs
的 emitter
对象,JQueryStyleEventEmitter
则允许传入一个 jq
元素对象
eventName
事件名称,例如 click
、focus
options
类型签名:
export interface EventListenerOptions {
capture?: boolean;
passive?: boolean;
once?: boolean;
}
复制代码
就是针对 target
是 dom
元素的情况下,addEventListener
的第三个参数
resultSelector
相当于一个拦截器,可以决定最终传递给 subscribe
的值是什么,例如
let i = 0
fromEvent(div, 'click', (v) => {
if (i++ % 2 === 0) {
return v
}
return null
}).subscribe(e => {
console.log('click', e);
})
复制代码
第偶数次 click
事件的触发,subscribe
接收到的 e
是点击事件,而如果是第奇数次,则 e
就是 null
下面看源码
// rxjs/src/internal/observable/fromEvent.ts
export function fromEvent<T>(
target: any,
eventName: string,
options?: EventListenerOptions | ((...args: any[]) => T),
resultSelector?: (...args: any[]) => T
): Observable<T> {
// ...
const [add, remove] =
// If it is an EventTarget, we need to use a slightly different method than the other two patterns.
isEventTarget(target)
? eventTargetMethods.map((methodName) => (handler: any) => target[methodName](eventName, handler, options as EventListenerOptions))
: // In all other cases, the call pattern is identical with the exception of the method names.
isNodeStyleEventEmitter(target)
? nodeEventEmitterMethods.map(toCommonHandlerRegistry(target, eventName))
: isJQueryStyleEventEmitter(target)
? jqueryMethods.map(toCommonHandlerRegistry(target, eventName))
: [];
}
复制代码
isEventTarget
、isNodeStyleEventEmitter
、isJQueryStyleEventEmitter
分别用于判断 target
是 dom
元素还是 node event
还是 jq
对象,判断完了之后对不同类型的 target
执行对应的方法,返回 add
和 remove
// rxjs/src/internal/observable/fromEvent.ts
const nodeEventEmitterMethods = ['addListener', 'removeListener'] as const;
const eventTargetMethods = ['addEventListener', 'removeEventListener'] as const;
const jqueryMethods = ['on', 'off'] as const;
复制代码
这里其实就是拿到 target
的添加监听事件方法和移除监听事件方法,然后在订阅的时候给 target
注册上监听事件,并在订阅清除的时候同时移除监听事件
// rxjs/src/internal/observable/fromEvent.ts
export function fromEvent<T>(
target: any,
eventName: string,
options?: EventListenerOptions | ((...args: any[]) => T),
resultSelector?: (...args: any[]) => T
): Observable<T> {
//...
return new Observable<T>((subscriber) => {
const handler = (...args: any[]) => subscriber.next(1 < args.length ? args : args[0]);
add(handler);
return () => remove!(handler);
});
}
复制代码
timer & interval
timer: 延迟发送通知(
Used to emit a notification after a delay
)interval: 周期性地发送通知(即轮询)(
Emits incremental numbers periodically in time
)
// rxjs/src/internal/observable/timer.ts
export function timer(
dueTime: number | Date = 0,
intervalOrScheduler?: number | SchedulerLike,
scheduler: SchedulerLike = asyncScheduler
): Observable<number> {}
复制代码
timer
的第一个参数是延迟时间,即决定延迟多长时间后执行,所以这是一个异步操作,内部会用到 scheduler
if (due < 0) {
due = 0;
}
let n = 0;
return scheduler.schedule(function () {
// ...
}, due);
复制代码
关于 scheduler
之前文章已经说过了,这里就不再多说了,这里 scheduler.schedule(function () {}, due);
你可以看成是 setTimeout(function () {}, due);
第二个参数 intervalOrScheduler
,如果传入的是一个数字,则表示在延迟 dueTime
时间之后,每隔 intervalOrScheduler
时间执行一次,就相当于是 setInterval
,而实现轮询的方法,就是在 schedule
里再调用 schedule
return scheduler.schedule(function () {
if (!subscriber.closed) {
// Emit the next value and increment.
subscriber.next(n++);
if (0 <= intervalDuration) {
// If we have a interval after the initial timer,
// reschedule with the period.
this.schedule(undefined, intervalDuration);
} else {
// We didn't have an interval. So just complete.
subscriber.complete();
}
}
}, due);
复制代码
你也可以传入一个 scheduler
,用于自定义调度,而如果你第二个参数传入了数字,并且还想传入一个 scheduler
的话,那么可以在第三个参数传入
interval
是 timer
的一个封装版,可以看成是 rxjs
中的 setInterval
// /rxjs/src/internal/observable/interval.ts
export function interval(period = 0, scheduler: SchedulerLike = asyncScheduler): Observable<number> {
if (period < 0) {
period = 0;
}
return timer(period, period, scheduler);
}
复制代码
Join Creation Operators
concat
concat
可以将数个 Observables
组合成一个新的 Observable
,并且在每个 Observable
结束后才继续执行下一個 Observable
const timer1 = interval(300).pipe(take(1));
const timer2 = interval(500).pipe(take(2));
const timer3 = interval(500).pipe(take(2));
复制代码
对于上述代码,想让 timer1
执行完了再开始执行 timer2
,timer2
执行完了再开始执行 timer3
,如果不借助 concat
可以这么写:
timer1.subscribe({
next: console.log,
complete: () => timer2.subscribe({
next: console.log,
complete: () => timer3.subscribe({
next: console.log
})
})
})
复制代码
这是我们应当避免写的 Callback Hell
,如果借助 concat
就好多了
const result = concat(timer1, timer2, timer3)
result.subscribe(x => console.log(x))
复制代码
// /rxjs/src/internal/observable/concat.ts
export function concat(...args: any[]): Observable<unknown> {
return concatAll()(from(args, popScheduler(args)));
}
// /rxjs/src/internal/util/args.ts
export function popScheduler(args: any[]): SchedulerLike | undefined {
return isScheduler(last(args)) ? args.pop() : undefined;
}
复制代码
concatAll
下面会提到,from
前文说过,就是将输入的值转换为 Observable
的方法,args
是 concat
的参数,也就是即将合并的数据流
那么源码的意思就比较明显了,即将 concat
接收的数据流一个个地传递给 concatAll
,由于 concatAll
具有顺序化执行数据流的功能,所以 concat
传入的数据流也就必须一个个执行,上一个数据流结束了才能轮到下一个
Transformation Operators
mergeMap
把所有
Observable
合并到同一个数据流內,不会退订上一次的Observable
,也不会等待上一次的Observable
结束,平行处理所有的Observable
const letters = of('a', 'b', 'c')
const result = letters.pipe(
mergeMap(x => interval(1000).pipe(map(i => x + i)))
)
result.subscribe(x => console.log(x))
// a0
// b0
// c0
// a1
// b1
// c1
// ...
复制代码
等价于
const letters = of('a', 'b', 'c')
letters.subscribe({
next: x => {
interval(1000).pipe(map(i => x + i)).subscribe(x => console.log(x))
}
})
复制代码
所以实际上,mergeMap
就是进一步的封装(当然了,rxjs
内基本所有操作符都是进一步封装的结果),如果存在 resultSelector
,那么无需你在手动去 subscribe
了,直接 mergeMap
就行了
看 mergeMap
的源码,看到主要是调用了 mergeInternals
,所以直接看这个
// /rxjs/src/internal/operators/mergeInternals.ts
const doInnerSub = (value: T) => {
// ...
innerFrom(project(value, index++)).subscribe(
new OperatorSubscriber(
subscriber,
(innerValue) => {
// 。。。
subscriber.next(innerValue);
},
// ...
)
复制代码
主要的就是 subscriber.next(innerValue)
这一句 project
是作为参数传入 mergeMap
的函数,value
是原 Observable
发出的值,index
代表是 Observable
第几次(从 0
开始)发出的事件,innerFrom
上面说过了,是将传入的参数转为 Observable
的方法
在这里,对 mergeMap
传入的 Observable
进行了 subscribe
,每次 Observable
发出值的时候,就会执行 subscriber.next(innerValue)
,而 subscriber
就是最外面 mergeMap
隶属的那条数据流
那么数据流动就很清晰了,mergeMap
对所有内部产生的数据流进行了订阅,并且在这些数据流产生数据的同时,对数据进行加工,再传给最外面唯一的主数据流,即数据的出口只有一个,从而给外界产生单数据流的结果
map
类似于 Array.prototype.map(),将
Observable
每次的值通过转换函数转成另外的值
const letters = of('a', 'b', 'c');
const result = letters.pipe(map((v, i) => v + i));
result.subscribe(x => console.log(x));
// a0
// b1
// c2
复制代码
// /rxjs/src/internal/operators/map.ts
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate((source, subscriber) => {
let index = 0;
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
复制代码
operate
是 rxjs
内部创建操作符的通用方法,很多操作符都会使用这个方法来创建,其内部主要是调用了 Observable
的 lift
方法,方便操作符之间的链式调用的,这个之前文章已经说过了,至于 OperatorSubscriber
也就是一个 Subscriber
,不再多说
map
里也是调用了 Observable
的 subscribe
方法,主要是 project.call(thisArg, value, index++)
这一句,project
就是 map
传入的那个函数参数,这个函数接收两个参数,第一个参数就是原始 Observable
发出的值,第二个参数记录目前的值是 Observable
第几次(从 0
开始)发出的事件,经过 map
处理之后的结果再交给 subscriber.next
做为最终值发出
可以看到,map
回调函数的第二个参数 index
,其初始化是在 subscribe
外面,所以每 subscribe
一次,其值就会递增
scan
作用类似于
reduce
,并且会在每次更新的时候抛出当前的累计值
of(1, 2, 3).pipe(
scan((total, c) => total + c, 0)
).subscribe(console.log)
// 1
// 3
// 6
复制代码
scan
内部调用了 scanInternals
,直接看这个
// /rxjs/src/internal/operators/scanInternals.ts
let hasState = hasSeed;
let state: any = seed;
let index = 0;
source.subscribe(
createOperatorSubscriber(
subscriber,
(value) => {
const i = index++;
state = hasState
? // We already have state, so we can get the new state from the accumulator
accumulator(state, value, i)
: // We didn't have state yet, a seed value was not provided, so
// we set the state to the first value, and mark that we have state now
((hasState = true), value);
// Maybe send it to the consumer.
emitOnNext && subscriber.next(state);
},
// ..
)
);
复制代码
scanInternals
内部维护了两个变量 state
、index
,分别用户存储当前计算的累计值和当前累计的次数(从 0
开始),每次订阅的时候,都会将 state
累加上新传入的值,且将 index
递增
switchMap
将上游的
Observable
映射成另外的Observables
,并且每次接到上游数据流的时候,都会取消对前一个映射数据流的订阅
timer(0, 2000).pipe(
map(() => interval(500)),
mergeAll(),
).subscribe(console.log)
// 0,1,2,3... 0,1,2,3...
复制代码
每隔 2000ms
,上游就会产生新的数据流,被 switchMap
转换成其他的数据流,同时会取消对前一个数据流的订阅,所以上述代码会循环输出 0 1 2 3 4
而如果每次生成新数据流的时候,不取消对前一个数据流的订阅,那么就会出现多个数据流同时存在的情况,输出的结果就是多条数据流订阅结果的混合了,这个特性也正是 switchMap
和其他打平操作符的主要区别
// /rxjs/src/internal/operators/switchMap.ts
innerSubscriber?.unsubscribe();
let innerIndex = 0;
const outerIndex = index++;
// Start the next inner subscription
innerFrom(project(value, outerIndex)).subscribe(
(innerSubscriber = createOperatorSubscriber(
subscriber,
(innerValue) => subscriber.next(resultSelector ? resultSelector(value, innerValue, outerIndex, innerIndex++) : innerValue),
() => {
innerSubscriber = null!;
checkComplete();
}
))
);
复制代码
innerSubscriber
就是上一个订阅的数据流,每次转换新的数据流之前,会调用 innerSubscriber?.unsubscribe();
,也就是取消对数据流的订阅,innerFrom
前面说过了,就是生成 observable
的方法,取消和重新生成的逻辑就在这短短几行代码里了
Filtering Operators
take
设定只从
Observable
中接收N
次事件的值。当订阅开始后,如果发生过的事件次数已经达到我们设定的,就会结束当前数据流
interval(500).pipe(
take(3)
).subscribe(console.log)
// 0
// 1
// 2
复制代码
// /rxjs/src/internal/operators/take.ts
export function take<T>(count: number): MonoTypeOperatorFunction<T> {
return count <= 0
() => EMPTY
: operate((source, subscriber) => {
let seen = 0;
source.subscribe(
createOperatorSubscriber(subscriber, (value) => {
if (++seen <= count) {
subscriber.next(value);
if (count <= seen) {
subscriber.complete();
}
}
})
);
});
}
// /rxjs/src/internal/observable/empty.ts
export const EMPTY = new Observable<never>((subscriber) => subscriber.complete());
复制代码
当设定次数小于 0
时,立即结束(完成)当前数据流,这是个边界判断
否则,会有个变量 seen
进行计数,每次 next
之前,此值会递增,当 count <= seen
的时候,说明已经执行了我们预设的数字,此时结束(完成)当前数据流
Join Operators
mergeAll
mergeAll
和 mergeMap
的作用很相似,实际上 mergeAll
内部就调用了 mergeMap
,区别点在于,上游的数据流在经过 mergeMap
的时候,会被 mergeMap
内部传入的事先定义好的函数进一步处理,得到新的数据结果,然后再让数据结果继续往下流动;而 mergeAll
只是对上游的数据进行兼容处理,无法做额外的数据处理,实际上,mergeMap
完全可以取代 mergeAll
,源码层面,mergeAll
也是调用了 mergeMap
,算是进一步的功能封装
概念不好理解,看例子会更清晰些
of(1).pipe(
map(v => v)
).subscribe(console.log)
// 1
复制代码
上述代码的输出明显,就是 1
,现在改一下
of(1).pipe(
map(v => of(v))
).subscribe(console.log)
// Observable2 {_subscribe: ƒ}
复制代码
这时输出的是一个 Observable
,想要取得这个 Observable
的值,还得再次 subscribe
一次
of(1).pipe(
map(v => of(v)),
).subscribe(v => v.subscribe(console.log))
复制代码
而如果借助 mergeAll
of(1).pipe(
map(v => of(v)),
mergeAll()
).subscribe(console.log)
// 1
复制代码
就不需要进一步 subscribe
,本例子还可以使用 mergeMap
of(1).pipe(
map(v => of(v)),
mergeMap(v => v)
).subscribe(console.log)
// 1
复制代码
但 mergeMap
能做得更多,例如,可以改变原数据的值
of(1).pipe(
map(v => of(v)),
mergeMap(v => v.pipe(map(n => n *2)))
).subscribe(console.log)
// 2
复制代码
原数据是 1
,经过 mergeMap
处理后变成了 2
mergeAll
可以接收一个参数,这个参数能够控制同时最多并发订阅的 observable
对象数量
of(0, 1, 2, 3).pipe(
map(v => of(v).pipe(delay(1000))),
mergeAll()
)
.subscribe(console.log)
复制代码
上述代码将在执行后 1000ms
的时候,同时输出 0 1 2 3
of(0, 1, 2, 3).pipe(
map(v => of(v).pipe(delay(1000))),
mergeAll(2)
)
.subscribe(console.log)
复制代码
上述代码将在执行后 1000ms
的时候,同时输出 0 1
,然后再等待 1000ms
,再同时输出 2 3
由于 mergeMap
是完全可以取代 mergeAll
的,所以 mergeMap
也可以实现
of(0, 1, 2, 3).pipe(
map(v => of(v).pipe(delay(1000))),
mergeMap(v => v, 2)
)
.subscribe(console.log)
复制代码
mergeAll
内部调用了 mergeMap
,最终都是调用了 mergeInternals
// /rxjs/src/internal/operators/mergeInternals.ts
const outerNext = (value: T) => (active < concurrent ? doInnerSub(value) : buffer.push(value));
复制代码
这里有个判断 active < concurrent
,active
初始值为 0
,并且每执行一次 doInnerSub(value)
就会累计 1
,concurrent
是 mergeAll
的参数,如果不传这个值就是 Infinity
,也就是说,如果 mergeAll
不传参,则这个表达式永远执行 doInnerSub(value)
doInnerSub
就是执行数据流订阅的方法,当同时在执行中的 observable
数量大于 concurrent
时,就会将后面的数据流数据先暂存到 buffer
中,当有 observable
执行完毕后,则将 active--
,并对 buffer
里暂存的数据生成 observable
并进行订阅,即执行 doInnerSub
// /rxjs/src/internal/operators/mergeInternals.ts
try {
active--;
while (buffer.length && active < concurrent) {
const bufferedValue = buffer.shift()!;
if (innerSubScheduler) {
executeSchedule(subscriber, innerSubScheduler, () => doInnerSub(bufferedValue));
} else {
doInnerSub(bufferedValue);
}
}
checkComplete();
} catch (err) {
subscriber.error(err);
}
复制代码
concatAll
concatAll
实际上是 concat
的高阶 Observable
操作符,就是说 concatAll
不需要亲自一个个地去接收数据流作为参数,然后再合并了,它只要在 pipe
中无脑接收上游数据流就行
// /rxjs/src/internal/observable/concat.ts
export function concatAll<O extends ObservableInput<any>>(): OperatorFunction<O, ObservedValueOf<O>> {
return mergeAll(1);
}
复制代码
经过前面 mergeMap
和 mergeAll
源码的讲解,concatAll
就很清晰了,就是执行了 mergeAll(1)
,意思就是每次只能同时订阅一个数据流,上一个数据流结束了才能开始下一个
of(0, 1, 2).pipe(
map(v => of(v).pipe(delay(1000))),
concatAll()
).subscribe(console.log)
复制代码
上述代码执行后,经过 1s
输出 0
,再经过 1s
输出 1
,再经过 1s
输出 2
,结束
Utility Operators
tap
拦截源上的每个
observable
的数据并对数据进行修改,接着再把这个observable
继续传递下去
一般而言,operator
在处理完上游的数据之后,会产生一个新的 observable
往下继续传递,但是 tap
不同,它虽然也会修改数据流,但并不会产生新的 observable
,它只是修改上游的 observable
,然后再把修改后的 observable
传递下去,所以会有 副作用
tap
接收的参数和 observable.subscribe
一样,所以你可以把它看成是一个订阅操作
interval(500).pipe(
tap(console.log),
map(n => n % 2 ? 'even' : 'odd')
).subscribe(console.log)
// 0
// even
// 1
// add
// ...
复制代码
// /rxjs/src/internal/operators/tap.ts
source.subscribe(
createOperatorSubscriber(
subscriber,
(value) => {
tapObserver.next?.(value);
subscriber.next(value);
},
() => {
isUnsub = false;
tapObserver.complete?.();
subscriber.complete();
},
(err) => {
isUnsub = false;
tapObserver.error?.(err);
subscriber.error(err);
},
() => {
if (isUnsub) {
tapObserver.unsubscribe?.();
}
tapObserver.finalize?.();
}
)
);
复制代码
subscriber
执行什么操作, tapObserver
就执行什么操作,并且 tapObserver
的操作在 subscriber
之前。 subscriber
外部是拿不到的,而外部却能控制 tapObserver
,所以算是一种拦截处理
Mathematical and Aggregate Operators
reduce & max
reduce
和 scan
很类似,二者都是调用了 scanInternals
,只是参数值不同罢了,唯一的区别是,scan
会在每次遍历时将当前累加值 emit
出来,而 reduce
只在数据流完成(complete
)的时候,才会把最终的累计值 emit
出来,所以使用 reduce
的时候,必须要保证数据流能够结束(complete
),否则 reduce
不会有结果的
max
类似于 Math.max
,用于查找当前数据流中最大的数据值,当然,最大
这个概念实际上取决于你传入的 comparer
函数 利用 reduce
// /rxjs/src/internal/operators/max.ts
export function max<T>(comparer?: (x: T, y: T) => number): MonoTypeOperatorFunction<T> {
return reduce(isFunction(comparer) ? (x, y) => (comparer(x, y) > 0 ? x : y) : (x, y) => (x > y ? x : y));
}
复制代码
interval(100).pipe(
map(() => of(Math.random())),
mergeAll(),
take(5),
max((a, b) => a < b ? -1 : 1),
).subscribe(console.log)
// 0.9138789699353005
复制代码
小结
刚开始看 operator
的时候,会感觉很乱,但当摸清楚了套路的时候,就知道哪些是主干哪些是无关紧要的细枝末节了,operator
是 rxjs
中使用频率最高的 api
,了解了其实现原理,有助于我们自行实现定制化的 operator