已经学习了很久的 angular 了,rxjs 中的操作数也有很多都打过了照面,subject 也已经可以仿着别人的栗子,能写个大概的轮廓了,可是我真的理解了、掌握了 rxjs 中的 subject 吗?
以前有人告诉过我:在生活中学到的所有东西中,真正让人难以忘怀的就是我们教给其他人的东西。所以现在就把它慢慢整理出来,如果没有理解透彻 subject 的你看到了,很开心你会有所收获;
背景:为什么我们需要 subject ?
理解 hot Observable 和 cold observable
hot Observable: 每次被 subscribe 都会产生一个全新的数据序列数据流;(创建类操作符、interval、range);也就是虽然对一个 Observable 做了多次 subscribe, 但是对于每一个 Observer, 其实都有一个独立的数据流在喂着,数据并不是真正来自于同一个源头;也就是不同订阅者的订阅实则是相互独立,互不干扰的;比如下面这个例子:
const source$ = interval(500).pipe(take(3));
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete')
};
const observerB = {
next: value => console.log('b' + value),
error: error => console.log('b' + error),
complete: () => console.log('b complete')
};
console.log(source$.subscribe(observerA));
setTimeout(() => {
console.log(source$.subscribe(observerB));
}, 1000)
上面 observerA和observerB分别订阅了source,从 log 可以看出两个观察者都接收到了完整的数据序列;也就是这两个 observer 是分开执行,是独立的;
Hot Observable: 在创建类操作符中,fromPromise、fromEvent、fromEventPattern 是hot Observable;不难看出,产生 hot Observable 的操作符数据源在外部,或来自 promise,或来自DOM,或来自Event emitter,真正的数据源和有没有 Observer 没有任何关系; 比如以下这个栗子:
const source$ = interval(500).pipe(take(3), share());
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete')
};
const observerB = {
next: value => console.log('b' + value),
error: error => console.log('b' + error),
complete: () => console.log('b complete')
};
console.log(source$.subscribe(observerA));
setTimeout(() => {
console.log(source$.subscribe(observerB));
}, 1000);
从 log 可以看出,当observerB 订阅时,接收到的元素是接着observerA的订阅往下发的;而不是从新开始;
满足Hot Observable 的例子不少,比如浏览器中鼠标的移动事件、点击时间、浏览器中的滚动事件,来自 websocket 的推送消息,还有 nodejs 支持的EventEmitter 对象消息;
现在有这么一种情况:如果我们想要在定一个栗子的基础上,我们订阅source 不会从头开始接收元素,而是从上次订阅到当前处理的元素开始接受元素,我们该怎么办呢?也就是我们想要把 cold Observable 转换成 hot Observable ,我们该如何操作?
subject 实现机制和使用
获取有人已经想到可以通过一个中介来实现,也就是中介来订阅 source, 让中介把数据转送出去;
const source$ = interval(500).pipe(take(3));
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete')
};
const observerB = {
next: value => console.log('b' + value),
error: error => console.log('b' + error),
complete: () => console.log('b complete')
};
const subject = {
observers: [],
subscribe: () => this.observers.push(observer),
next: () => this.observers.foreach(o => o.next(error)),
error: () => this.observers.foreach(o => o.error(error)),
complet: () => this.observers.foreach(o => o.com[lete())
}
source$.subscribe(subject);
console.log(subject.subscribe(observerA);));
setTimeout(() => {
console.log(source$.subscribe(observerB));
}, 1000)
// A0
// A1
// B1
// A2
// B2
// A complete
// B complete
从这个代码可以看出,这个中介就是 subject,我们创建了一个 subject 变量,它同时具备 observer 所有的方法(next, error, complete), 还有 observable 的 subscribe ;每当有值送出,就会遍历清单中所有的observer,并再次把值送出,这样一来不管多久之后加进来的 observer, 都会是从当前处理到的元素接着往下走;把上面的代码改成 rxjs 中subject 代码是这样的:
const source$ = interval(500).pipe(take(3));
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete')
};
const observerB = {
next: value => console.log('b' + value),
error: error => console.log('b' + error),
complete: () => console.log('b complete')
};
const subject = new Subject();
source$.subscribe(subject);
console.log(subject.subscribe(observerA);));
setTimeout(() => {
console.log(source$.subscribe(observerB));
}, 1000)
// A0
// A1
// B1
// A2
// B2
// A complete
// B complete
打印结果是一致的;也就是说 subject 的执行机制其实跟我们猜想的一样;建立一个 subject 先订阅 observable(数据源),再把我们的真正的 observer 加到 subject 中,这样一来就能完成订阅,而每个加到subject 中的 observer 都能整组的接收到相同的元素;总结出来就是两句话:
- Subject 同时是 Observable,又是 Observer;
- Subject 会对内部的Observable 清单进行多播;
我们还可以看看 subject 的源码:
export class Subject<T> extends Observable<T> {
observers: Observer<T>[] = [];// 订阅者列表
closed = false;
isStopped = false;
hasError = false;
constructor() {
super();
}
next(value?: T) {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
if (!this.isStopped) {
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) {
copy[i].next(value!);
}
}
}
error(err: any) {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
this.hasError = true;
this.thrownError = err;
this.isStopped = true;
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) {
copy[i].error(err);
}
this.observers.length = 0;
}
complete() {
if (this.closed) {
throw new ObjectUnsubscribedError();
}
this.isStopped = true;
const { observers } = this;
const len = observers.length;
const copy = observers.slice();
for (let i = 0; i < len; i++) {
copy[i].complete();
}
this.observers.length = 0;
}
unsubscribe() {
this.isStopped = true;
this.closed = true;
this.observers = null!;
}
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
}
Subject既是Observable又是Observer, 是Observable是因为它继承于Observable 类, 是 Observer是因为它也有 next、error、complete方法,可用作subscribe的参数。 不同于Observable,它可以向多个Observer多路推送数值。普通的Observable并不具备多路推送的能力(每一个Observer都有自己独立的执行环境),而 Subject可以共享一个执行环境。
subject 的使用
我们可以用 subject 的next 方法传送值,所有订阅的 observer 就会接收到,又因为 subject 本身是 observable , 所以这样的使用方式很适合在某些无法直接使用 observable 的情况中;
subject 三种变形:BahaviorSubject、ReplaySuject、AsyncSubject;
- BehaviorSubject: 很多时候我们会希望 subject 能代表当下的状态,而不是单纯的事件发送,也就是说如果今天有一个新的订阅,我们希望 subject 能立即给出最新的值,而不是没有回应,例如下面:
const subject = new Subject();
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete!')
}
const observerB = {
next: value => console.log('B' + value),
error: error => console.log('B' + error),
complete: () => console.log('B complete!')
}
subject.subscribe(observerA);
subject.next(1);
// A1
subject.next(2);
// A2
subject.next(3);
// A3
setTimeout(() => {
subject.subscribe(observerB); // 3 s才订阅,b 不会收到任何值
},3000)
以这个例子来说, observerB 订阅之后,是不会有任何元素送给 observerB 的, 因为在这之后没有执行任何 subject.next(), 但很多时候我们希望 subject 能表达当前的状态,在一订阅时就能收到最新的状态是什么,而不是订阅之后要等到有变动才能接受到新的额状态,以这个例子来说,我们希望 observerB 马上就能收到 3,这是就可以用 BehaviorSubject;
- BehaviorSubject:它跟Subject 最大的不同是前者是用来呈现当前的值,而不是单纯的发送事件;前者会记住最新一次发送的元素,并把该元素当成目前的值,在使用上前者需要传入一个参数来代表起始的状态;如
const subject = new Subject(0);// 0为起始值
- ReplaySubject: 在某些时候我们会希望 Subject 代表事件,但又能在新订阅时重新发送最后的几个元素,这时就用这个;
const subject = new ReplaySubject(2); // 重复发送最后两个元素
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete!')
}
var observerB = {
next: value => console.log('B' + value),
error: error => console.log('B' + error),
complete: () => console.log('B complete!')
}
subject.subscribe(observerA);
subject.next(1);
// A1
subject.next(2);
// A2
subject.next(3);
// A3
setTimeout(() => {
subject.subscribe(observerB);
// B2
// B3
},3000)
可能会有人以为 ReplaySubject(1) 是不是就等于 BehaviorSubject,其实是不一样的;BehaviorSubject在建立时就有初始值,代表的是初始状态,但ReplaySubject 只是事件的重放而已;
- AsyncSubject: 会在subject 结束后发送出最后一个值;如下面这个例子,AsyncSubject 用得比较少;
const subject = new AsyncSubject();
const observerA = {
next: value => console.log('A' + value),
error: error => console.log('A' + error),
complete: () => console.log('A complete!')
}
const observerB = {
next: value => console.log('B' + value),
error: error => console.log('B' + error),
complete: () => console.log('B complete!')
}
subject.subscribe(observerA);
subject.next(1);
subject.next(2);
subject.next(3);
subject.complete();
// "A3"
// "A complete!"
setTimeout(() => {
subject.subscribe(observerB);
// "B3"
// "B complete!"
},3000)
来源:oschina
链接:https://my.oschina.net/hyzccc/blog/3196594