Rxjs : 一些常考题

167 阅读2分钟

1️⃣ 冷 Observable 和 热 Observable 的区别?

📌 面试重点:考察对 RxJS 订阅行为的理解。

  • 冷 Observable(Cold Observable):每次订阅都会重新执行,数据流是独立的。
    示例(每次订阅都会重新触发 of(1,2,3)):

    const cold$ = of(1, 2, 3);
    cold$.subscribe(console.log); // 输出 1, 2, 3
    cold$.subscribe(console.log); // 重新执行,输出 1, 2, 3
    
  • 热 Observable(Hot Observable):所有订阅者共享同一数据流,不会因新订阅而重新执行。
    示例interval(1000) 是热的,所有订阅者共享同一计时器):

    import { interval } from 'rxjs';
    import { share } from 'rxjs/operators';
    
    const hot$ = interval(1000).pipe(share());
    
    hot$.subscribe(x => console.log('订阅 1:', x));
    
    setTimeout(() => {
      hot$.subscribe(x => console.log('订阅 2:', x));
    }, 3000);
    

    result:

    订阅 1: 0
    订阅 1: 1
    订阅 1: 2
    订阅 1: 3
    订阅 2: 3     👈 和订阅 1 同步收到值
    订阅 1: 4
    订阅 2: 4
    订阅 1: 5
    订阅 2: 5
    
    

📌 总结

  • 冷 Observable = 惰性,每次订阅都会重新执行
  • 热 Observable = 主动,所有订阅者共享同一数据流
  • shareReplay() 可以让冷变热,避免重复 API 请求。

2️⃣ switchMapexhaustMap 的区别?

📌 面试重点:考察 RxJS 并发控制。

操作符作用适用场景
switchMap取消上一个订阅,切换到最新的流搜索框请求(只关心最新输入)
exhaustMap忽略新的订阅,直到当前流完成表单提交(防止重复提交)

示例(switchMap:取消之前的请求)

fromEvent(input, 'input').pipe(
  switchMap(event => http.get(`/search?q=${event.target.value}`))
).subscribe(console.log);

🚀 新输入时,旧请求会被取消,适用于搜索框


示例(exhaustMap:防止重复点击提交)

fromEvent(button, 'click').pipe(
  exhaustMap(() => http.post('/submit'))
).subscribe(console.log);

📌 新点击会被忽略,直到当前请求完成,适用于防止表单重复提交


3️⃣ 如何处理 RxJS 中的错误?

📌 面试重点:考察对 catchErrorretry 的使用。

方法 1:catchError

作用:捕获错误并返回默认值,防止流中断。

http.get('/data').pipe(
  catchError(err => {
    console.error('请求失败:', err);
    return of([]); // 返回默认值,防止流中断
  })
).subscribe(console.log);

方法 2:retry(重试)

作用:请求失败时自动重试。

http.get('/data').pipe(
  retry(3), // 失败后重试 3 次
  catchError(err => of([])) // 仍然失败时返回默认值
).subscribe(console.log);

方法 3:retryWhen(自定义重试策略)

作用:比如指数退避(每次重试间隔增加)。

http.get('/data').pipe(
  retryWhen(errors => errors.pipe(
    delay(1000), // 1 秒后重试
    take(3) // 最多重试 3 次
  ))
).subscribe(console.log);

📌 适用场景:网络不稳定时,自动延迟重试


4️⃣ mergeMapconcatMap 的区别?

📌 面试重点:考察 RxJS 并发控制和执行顺序。

操作符是否并发是否按顺序执行适用场景
mergeMap✅ 并发❌ 无序批量并发 API 请求
concatMap❌ 串行✅ 顺序执行批量上传、按顺序请求

示例(mergeMap:并发)

from([1, 2, 3]).pipe(
  mergeMap(id => http.get(`/user/${id}`))
).subscribe(console.log);

🚀 所有请求并行执行,适用于批量加载数据


示例(concatMap:顺序执行)

from([1, 2, 3]).pipe(
  concatMap(id => http.get(`/user/${id}`))
).subscribe(console.log);

📌 按顺序请求,等待上一个完成再执行下一个,适用于批量上传


5️⃣ 如何让多个 API 请求依赖执行?

这是 RxJS 面试中的高频题!面试官重点考察你是否理解 多个 API 请求之间的依赖顺序与并发控制,能否熟练使用:

  • switchMap(取消前一个)
  • concatMap(按顺序执行)
  • forkJoin(并发多个,全部完成后处理)

🔁 后一个请求依赖前一个请求的结果

👉 用 switchMapconcatMap

this.api.getUser().pipe(
  switchMap(user => this.api.getOrders(user.id)) // 用前一个结果作为输入
).subscribe(orders => {
  console.log('用户订单', orders);
});
  • switchMap: 新请求会取消旧的,适合实时输入、搜索框等。

✅ 什么时候用 concatMap

如果请求不能被取消,例如保存数据(POST),你希望一个接一个地执行:

from([user1, user2, user3]).pipe(
  concatMap(user => this.api.saveUser(user))
).subscribe();

🤝 场景 2:多个请求并发执行,等所有完成后再处理结果

👉 用 forkJoin

forkJoin({
  user: this.api.getUser(),
  settings: this.api.getUserSettings()
}).subscribe(({ user, settings }) => {
  console.log(user, settings);
});
  • 所有 observable 同时开始
  • 所有都完成才会发出结果(一次性)
  • 常用于加载多个独立数据源,比如页面初始化

mergeMap

fromEvent(button, 'click').pipe(
  mergeMap(() => this.api.getData()) // 每次点击都发请求
).subscribe(res => console.log(res));

🔍 总结对比表:

操作符特点常用场景
switchMap取消旧的、只保留最新的实时搜索、动态请求
concatMap严格按顺序,一个接一个提交表单、按序保存
forkJoin所有请求并发,等全部完成页面初始化加载
mergeMap并发执行多个并发批量上传
exhaustMap忽略新的直到旧的完成登录防重复点击

需要我做一个 StackBlitz 示例,演示以上三种场景的真实效果吗?🔍


6️⃣ RxJS 什么时候应该用 BehaviorSubject

📌 面试重点:考察对 BehaviorSubject 的理解。

  • 普通 Subject:新订阅者不会收到之前的值。
  • BehaviorSubject新订阅者可以收到最新的值,适用于存储状态

示例(BehaviorSubject 共享状态)

const user$ = new BehaviorSubject(null);

// 任何地方订阅都能拿到最新值
user$.subscribe(user => console.log('订阅 1:', user));

user$.next({ id: 1, name: 'Amanda' });

user$.subscribe(user => console.log('订阅 2:', user)); // 能拿到 { id: 1, name: 'Amanda' }

📌 适用于:存储**用户信息、主题模式(dark/light)**等全局状态。


📌 总结

题目重点
冷 vs. 热 Observable订阅行为:冷 Observable 重新执行,热 Observable 共享数据流
switchMap vs. exhaustMapswitchMap 取消上一个,exhaustMap 忽略新请求(防止重复提交)
错误处理catchErrorretryretryWhen
mergeMap vs. concatMapmergeMap 并行,concatMap 串行(适用于上传任务)
API 依赖执行switchMap(依赖请求),forkJoin(并行等全部完成)
BehaviorSubject存储状态,新订阅者能拿到最新值