EnriqueMark
Angular

Subject、Observable和Observer

2026-04 繁體中文

Observable的pipe那裡説到了供水,但那邊只是被動的供水——如果我想要中間往裡面加水並讓所有吃水者都喝上新的水呢?比如從地下水換成山泉水,帶有甘甜的滋味(嗯…)。這裡就得用Subject。不過這個地方我得稍微分出來說個東西,那就是ObservableObserver,這倆在用的時候都會有一個next出來,很容易搞混。next即操作水塔,同時也代表去水塔接水,也就是釋出方定義傳出資料和訂閲放接受資料。

// 指定範型 <number>,表示這個流會發送數字
const observable = new Observable((subscriber: Subscriber<number>) => {
  subscriber.next(1); // 這裡的 next 是 Subscriber 實作的方法
  subscriber.complete();
});

// 訂閱測試
observable.subscribe({
  next: (val) => console.log(val),
  complete: () => console.log('Done')
});

Subscriber<T>繼承了Observer<T>這個介面,而後者約定了必須要實現nexterrorcomplete三個方法:

interface Observer<T> {
  next: (value: T) => void;
  error: (err: any) => void;
  complete: () => void;
}

next的傳入元素在定義observable的時候通過subscriber.next(1)直接給定了,這也是為什麼下游直接next: (val) => console.log(val)能取到值的原因,它取到的正是這裡的內容。這裡搞明白了,我們就知道為啥Subject不同,因為它自身即是一個Observable,同時也是一個Observer

// 1. 建一個水塔,默認是地下水
const mySubject = new Subject<string>();
// 2. A 跑來接水
mySubject.subscribe(data => console.log('A 收到:', data));

// 3. 把山泉水接入進水塔!
mySubject.next('山泉水'); 
// 主控台印出: A 收到: 山泉水

這就意味著,此物件的操作不再只是單純的取水,還可以決定往裡面加什麼水。只要加了,後續所有的消費者,都會受到影響。所以實務上一般都不太會把Subject直接暴露出來,否則不可控情況下,下游會把整個資料流改的面目全非。

不過Subject具備一個「多播」 (Multicast)的特性,會導致其釋出的時候回調全部下游訂閱者(執行了subscribe方法的物件)的next,並把mySubject.next('山泉水')這個資料塞過去。也就是説,如果存在此時還未被觸發subscribe的物件,它不僅拿不到這個值,其他值也拿不到。就跟一個高壓廣播水管一樣,來了就全部強行噴出,之後就關閘,一滴水也不給你。這會導致的顯著問題就可以見以下場景:事件觸發機制下的操作,使用者點選之後才會去subscribe。那此時晚來者就會兩手空空,什麼都拿不到。

BehaviorSubject這時候就派上用場,它是一個持續作用的Subject,不僅會在第一輪塞給所有人山泉水,還會在後面有人來的時候也給他一份,可以説是一個「熱情的給水人」,不管什麼事來,你要了他就給,跟Subject那樣過期不候的高冷性格相對。實際使用的時候,大部分也都是用這個。

但問題來了,若有人就是嘴欠,嫌棄山泉水不行,非要喝地下水怎麼辦?當前的供水單一,只會讓下游取到山泉水——ReplaySubject就派上用場了。如果説前兩者,一個是無狀態的:Subject晚來了兩手空空;一個是隻有最新狀態:BehaviorSubject無論什麼時候來都給你最新的(山泉水);ReplaySubject則是包含了過去和現在指定範圍內的全部狀態。它可以讓下游消費者取到過去的N個資料,即讓後續的取水者無論山泉水還是地下水(取決呼叫時指定的範圍),都能拿到手。如果你真的就喜歡喝地下水,自己把山泉水丟棄即可,或者你都想要,全部喝了也無所謂。總之就是將處置權交給下游,水塔只管供水,不管你怎麼處置。