原生 Observable API 來(lái)了!能否取代每周下載 5200 萬(wàn)次的 RxJS?
在Web開(kāi)發(fā)中,異步事件處理始終是構(gòu)建響應(yīng)式、可擴(kuò)展應(yīng)用的核心挑戰(zhàn)。傳統(tǒng)方案依賴(lài)addEventListener進(jìn)行事件監(jiān)聽(tīng),但在處理復(fù)雜事件流時(shí),其命令式編程模型常導(dǎo)致代碼臃腫、難以維護(hù)且缺乏組合性。盡管開(kāi)發(fā)者通常通過(guò) RxJS 等響應(yīng)式編程庫(kù)解決此類(lèi)問(wèn)題,但這些第三方方案需要額外學(xué)習(xí)成本和包體積負(fù)擔(dān)。
目前,W3C 正積極推動(dòng) Observable API 作為瀏覽器原生標(biāo)準(zhǔn)落地。該提案受響應(yīng)式編程范式啟發(fā),引入聲明式的事件處理模型,通過(guò)可觀察對(duì)象和觀察者的解耦設(shè)計(jì),使開(kāi)發(fā)者能以更函數(shù)式的方式組合、轉(zhuǎn)換和操作事件流。
注意:Observable API 是一種實(shí)驗(yàn)性功能,目前,僅在 Chrome v135 及以上版本中可用,且需啟用“實(shí)驗(yàn)性 Web 平臺(tái)功能”標(biāo)志。
背景
傳統(tǒng) JavaScript 處理多異步事件時(shí)易陷入"回調(diào)地獄",代碼呈現(xiàn)深層嵌套結(jié)構(gòu)。RxJS通過(guò)事件流抽象解決了這一問(wèn)題,提供過(guò)濾、映射和組合事件的能力。Observable API則將此能力直接集成至瀏覽器,其核心優(yōu)勢(shì)包括:
? 聲明式組合:通過(guò)map/filter/merge等操作符鏈?zhǔn)教幚硎录?/p>
? 自動(dòng)資源管理:內(nèi)置訂閱生命周期管理,避免內(nèi)存泄漏
? 標(biāo)準(zhǔn)互操作:與Promise、Async Iterator等現(xiàn)代異步API無(wú)縫集成
? 零依賴(lài)成本:瀏覽器原生支持,無(wú)需第三方庫(kù)即可實(shí)現(xiàn)高性能事件流處理
使用場(chǎng)景
處理 DOM 事件
傳統(tǒng) addEventListener 需手動(dòng)管理訂閱和清理,易導(dǎo)致內(nèi)存泄漏。Observable API 提供聲明式監(jiān)聽(tīng),自動(dòng)綁定生命周期。
通過(guò) element.when(eventName) 監(jiān)聽(tīng)事件,返回可觀察對(duì)象,支持鏈?zhǔn)讲僮鞣?/p>
const button = document.getElementById("myButton");
 
// 監(jiān)聽(tīng)點(diǎn)擊事件
button.when("click")
  .subscribe({
    next: (event) => console.log("點(diǎn)擊坐標(biāo):", event.clientX, event.clientY),
    error: (err) => console.error("事件錯(cuò)誤:", err),
    complete: () => console.log("監(jiān)聽(tīng)已終止")
  });
 
// 自動(dòng)清理:當(dāng)按鈕從DOM移除時(shí),訂閱自動(dòng)取消優(yōu)勢(shì):
? 自動(dòng)資源管理:元素被銷(xiāo)毀時(shí)自動(dòng)取消訂閱,避免內(nèi)存泄漏。
? 鏈?zhǔn)讲僮鳎嚎蔁o(wú)縫銜接 map/filter 等操作符處理事件流。
? 與 Promise 集成:通過(guò) .toPromise() 可將事件流轉(zhuǎn)換為 Promise。
帶終止條件的事件流
場(chǎng)景:需統(tǒng)計(jì)按鈕點(diǎn)擊次數(shù),直到用戶(hù)點(diǎn)擊“停止”按鈕,傳統(tǒng)方案需維護(hù)狀態(tài)變量和多個(gè)監(jiān)聽(tīng)器。
使用 takeUntil 操作符在特定事件觸發(fā)時(shí)終止流,結(jié)合 reduce 聚合結(jié)果:
const countButton = document.getElementById("countBtn");
const stopButton = document.getElementById("stopBtn");
countButton.when("click")
  .takeUntil(stopButton.when("click")) // 點(diǎn)擊停止按鈕時(shí)終止流
  .reduce((count) => count + 1, 0)     // 初始值為0,每次點(diǎn)擊+1
  .then(total => console.log(`總點(diǎn)擊次數(shù):${total}`))
  .catch(err => console.error("統(tǒng)計(jì)失敗:", err));優(yōu)勢(shì):
? 聲明式終止:無(wú)需手動(dòng)管理標(biāo)志位或清除定時(shí)器。
? Promise 集成:.reduce() 返回 Promise,可直接處理最終結(jié)果。
? 錯(cuò)誤處理:通過(guò) catch 捕獲流中的異常。
事件過(guò)濾與轉(zhuǎn)換
場(chǎng)景:僅響應(yīng)特定子元素的點(diǎn)擊事件,并將事件對(duì)象轉(zhuǎn)換為坐標(biāo)數(shù)據(jù)。
鏈?zhǔn)绞褂?nbsp;filter 和 map 操作符實(shí)現(xiàn):
const container = document.getElementById("container");
container.when("click")
  .filter(e => e.target.matches(".interactive")) // 僅匹配.interactive元素
  .map(e => ({ x: e.clientX, y: e.clientY }))    // 轉(zhuǎn)換為坐標(biāo)對(duì)象
  .subscribe({
    next: ({x, y}) => console.log(`有效點(diǎn)擊坐標(biāo):(${x},${y})`)
  });優(yōu)勢(shì):
? 精準(zhǔn)過(guò)濾:利用 CSS 選擇器語(yǔ)法(matches)過(guò)濾目標(biāo)元素。
? 數(shù)據(jù)轉(zhuǎn)換:將原始事件對(duì)象映射為業(yè)務(wù)所需格式。
? 可組合性:可繼續(xù)鏈?zhǔn)秸{(diào)用其他操作符(如 debounce)。
WebSocket 數(shù)據(jù)流處理
場(chǎng)景:實(shí)時(shí)接收 WebSocket 消息,處理特定類(lèi)型的數(shù)據(jù)并在連接關(guān)閉時(shí)自動(dòng)清理。
通過(guò) WebSocket 對(duì)象的 when("message") 監(jiān)聽(tīng)消息,結(jié)合 takeUntil 監(jiān)聽(tīng)關(guān)閉事件:
const ws = new WebSocket("wss://api.example.com");
ws.when("message")
  .takeUntil(ws.when("close")) // 連接關(guān)閉時(shí)終止流
  .map(e => JSON.parse(e.data)) // 解析JSON數(shù)據(jù)
  .filter(data => data.type === "update") // 僅處理update類(lèi)型
  .subscribe({
    next: update => console.log("收到更新:", update),
    complete: () => console.log("連接已關(guān)閉")
  });自定義事件流
場(chǎng)景:創(chuàng)建定時(shí)計(jì)數(shù)器,每秒遞增并在達(dá)到閾值時(shí)自動(dòng)終止。
通過(guò) new Observable 構(gòu)造函數(shù)定義自定義流,利用 setInterval 和 subscriber 控制流程:
const observable = newObservable((subscriber) => {
let count = 0;
const id = setInterval(() => {
    if (count > 10) {
      subscriber.complete();
      return;
    }
    if (Math.random() < 0.1) {
      subscriber.error(newError("出錯(cuò)了!"));
      return;
    }
    subscriber.next(count++);
    subscriber.addTeardown(() => {
      console.log("清理!");
      clearInterval(id);
    });
  }, 1000);
});
observable.subscribe({
next: (value) =>console.log(`計(jì)數(shù): ${value}`),
error: (error) =>console.error(error),
complete: () =>console.log("完成!"),
});Observable 實(shí)例的方法
Observable 接口提供了多種方法,方便事件流的處理。以下是部分方法的總結(jié):
方法  | 描述  | 
  | 訂閱事件并接收通知  | 
  | 監(jiān)聽(tīng)事件直到指定事件發(fā)生  | 
  | 轉(zhuǎn)換事件  | 
  | 僅篩選滿(mǎn)足條件的事件  | 
  | 獲取前 n 個(gè)事件  | 
  | 跳過(guò)前 n 個(gè)事件  | 
  | 將每個(gè)事件映射到新流并展平  | 
  | 映射到新流并在切換時(shí)取消之前的訂閱  | 
  | 捕獲流中發(fā)生的錯(cuò)誤  | 
  | 指定流結(jié)束時(shí)調(diào)用的回調(diào)函數(shù)  | 
  | 將流中的事件轉(zhuǎn)換為數(shù)組  | 
  | 對(duì)每個(gè)事件執(zhí)行回調(diào)函數(shù)并獲取值  | 
  | 累積事件并轉(zhuǎn)換為單個(gè)值  | 
  | 獲取第一個(gè)事件  | 
  | 獲取最后一個(gè)事件  | 
  | 獲取滿(mǎn)足條件的第一事件  | 
  | 從可迭代對(duì)象創(chuàng)建 Observable 實(shí)例  | 
與 RxJS 的比較
RxJS 是一個(gè)全面的反應(yīng)式編程庫(kù),提供廣泛的操作符和功能,用于處理異步數(shù)據(jù)流,其 npm 周下載量高達(dá) 5200w+。Observable API 實(shí)際上是參考 RxJS 設(shè)計(jì)的。
? 范圍:RxJS 可以處理任何類(lèi)型的異步數(shù)據(jù)流,而 Observable API 主要針對(duì) EventTarget 對(duì)象的事件流,盡管通過(guò) new Observable() 可以更廣泛使用。
? 功能集:Observable API 提供了豐富的操作符,但可能不如 RxJS 全面,后者有更多操作符和更長(zhǎng)的開(kāi)發(fā)歷史。
因此,Observable API 可能在瀏覽器事件處理中取代 RxJS 的某些用途,而無(wú)法完全取代 RxJS,尤其在復(fù)雜場(chǎng)景或跨環(huán)境開(kāi)發(fā)中。
相關(guān)鏈接
? 規(guī)范:https://wicg.github.io/observable/
? 提案:https://github.com/WICG/observable
? Chrome 實(shí)施狀態(tài):https://chromestatus.com/feature/5154593776599040















 
 
 








 
 
 
 