首页 > 解决方案 > 如果主题超出范围时未取消订阅,订阅 rxjs 主题会导致内存泄漏吗?

问题描述

在我的应用程序中,我有一些代表当地货币的对象,还有一些代表货币汇率的对象。

我的问题是,如果我的本地货币对象订阅了货币对象上的单个主题以提醒汇率变化(但货币对象实际上并没有保存订阅),然后单一货币实例定义了所有这些订阅的主题是设置为 null,如果我没有对 50,000 个货币对象中的每一个调用取消订阅,所有这些“订阅”会消失吗?

对于一个具体(简化)的例子,这个:

import { Subject } from 'rxjs'
interface MyChangeEvent {
  oldValue : number;
  newValue : number;
}
export class Currency {
  rateSubject : Subject<MyChangeEvent>;
  private _rate : number;
  private _name : string;
  constructor(name : string, rate : number) {
    this.rateSubject = new Subject();
    this._rate= rate;
    this._name = name;
  }
  get rate() : number {
    return this._rate;
  }
  set rate(v : number) {
    let oldrate = this.rate;
    this._rate = v;
    let ce : MyChangeEvent
    ce = {} as MyChangeEvent;
    ce.newValue = v;
    ce.oldValue = oldrate;
    this.rateSubject.next(ce);
  }
}


export class Money {
  private _rate : number = 1;
  private _localCurr : number = 0;
 
  get dollarValue() {
    return this._localCurr * this._rate;
  }

  constructor(localCurr : number, curr : Currency) {
    this._localCurr = localCurr;
    this._rate = curr.rate;
    curr.rateSubject.subscribe((change)=>{
        this._rate = change.newValue;
    })
  }
}

const test = function() {
    let c = new Currency("USD", 1);
    let m = new Money(500, c);
    c.rate = .5;
    c=null;
}

所以我的问题是,假设我的应用程序中有 50,000 个货币对象,然后我c=null在此处的最后一行进行设置。我为所有这些货币对象设置的 50,000 个侦听器是否保留在内存中的某个位置,当货币对象超出范围时,它们是否都被垃圾收集了?

标签: typescriptrxjs

解决方案


编辑

您还可以查看RxJS:为什么使用 Subject 时会发生内存泄漏


我会说不会有内存泄漏。

这是基于我对实际发生内存泄漏的原因的理解。通常这类问题发生在源无限时(例如,不会完成/错误,例如组件使用的全局服务)。

例如,在 Angular 中,一种常见的模式是将应用程序范围的服务注入组件并订阅服务公开的可观察属性之一。

class Service {
  private usersSrc = new Subject();
  users$ = this.usersSrc.asObservable();
}

然后你会在你的组件中这样做:

class FooComponent {
  ngOnInit () {
    this.subscription = this.service.users$.subscribe(() => {} /* callback */)
  }
}

注意:这仅用于演示目的,因为您希望使用其他方法,这样您就不必手动订阅,例如异步管道

users$被订阅时,因为users$来自usersSrc,新创建的订阅者将被添加到 Subject 的订阅者列表中。该订阅者的下一个回调将是() => {}回调。

现在,当组件被销毁时(例如,由于导航到另一条路线),如果您不执行类似的操作this.subscription.unsubscribe(),则该订阅者仍将是该订阅者列表的一部分。该unsubscribe方法将从该列表中删除该订阅者。

因此,下次创建组件时ngOnInit,将添加一个新订阅者,但如果您不使用this.subscription.unsubscribe().


我会说将该源设置为 null 就足够了。

如果源恰好是 a Subject,您也可以使用Subject.unsubscribe,尽管它可能没有任何区别。

unsubscribe() {
  this.isStopped = true;
  this.closed = true;
  this.observers = null!;
}

这将是一个简化版本。您可以将其粘贴到您的控制台中。

src = {
 subscribers: [],
 addSubscriber(cb) {
  this.subscribers.push(cb);
  return this.subscribers.length - 1
 },
 removeSubscriber(idx) {
  this.subscribers.splice(idx, 1)
 },
 next (data) {
  this.subscribers.forEach(cb => cb(data));
 }
}

// the component
class Foo {
 
constructor () {
   this.subIdx = src.addSubscriber(() => { console.log('foo') })
 }

 onDestroy () {
  src.removeSubscriber(this.subIdx);
 }
}

// usage

// creating a new component
foo = new Foo() // Foo {subIdx: 0}

// sending data to subscribers
src.next('test')

// destroying the component - without calling `onDestroy`
foo = null


src.next('test') // the subscribers is still there
VM506:18 foo

foo = new Foo() // registering a new instance - Foo {subIdx: 1}

src.next('test2')
foo
foo

foo.onDestroy()
src.next('test2')
foo

推荐阅读