BehaviorSubject vs Observable?

Posted

技术标签:

【中文标题】BehaviorSubject vs Observable?【英文标题】:BehaviorSubject vs Observable? 【发布时间】:2017-01-22 11:21:44 【问题描述】:

我正在研究 Angular RxJs 模式,但我不明白 BehaviorSubjectObservable 之间的区别。

据我了解,BehaviorSubject 是一个可以随时间变化的值(可以订阅并且订阅者可以接收更新的结果)。这似乎与Observable 的目的完全相同。

您什么时候使用ObservableBehaviorSubject?使用BehaviorSubject 比使用Observable 有什么好处,反之亦然?

【问题讨论】:

这篇文章特别帮助我以 ELI5 方式理解 observables vs subject vs behavior subject javascript.plainenglish.io/… 【参考方案1】:

BehaviorSubject 是一种主题,主题是一种特殊类型的 observable,因此您可以像订阅任何其他 observable 一样订阅消息。 BehaviorSubject 的独特之处在于:

它需要一个初始值,因为它必须始终在订阅时返回一个值,即使它没有收到next() 订阅后,它会返回主题的最后一个值。常规的 observable 仅在收到 onnext 时触发 在任何时候,您都可以使用getValue() 方法以不可观察的代码检索主题的最后一个值。

与可观察对象相比,对象的独特特征是:

除了是可观察对象之外,它还是观察者,因此除了订阅对象之外,您还可以向主题发送值。

此外,您可以使用BehaviorSubject 上的asObservable() 方法从行为主题中获取可观察对象。

Observable 是 Generic,BehaviorSubject 在技术上是 Observable 的子类型,因为 BehaviorSubject 是具有特定品质的 observable。

BehaviorSubject 示例:

// Behavior Subject

// a is an initial value. if there is a subscription 
// after this, it would get "a" value immediately
let bSubject = new BehaviorSubject("a"); 

bSubject.next("b");

bSubject.subscribe(value => 
  console.log("Subscription got", value); // Subscription got b, 
                                          // ^ This would not happen 
                                          // for a generic observable 
                                          // or generic subject by default
);

bSubject.next("c"); // Subscription got c
bSubject.next("d"); // Subscription got d

带有常规主题的示例 2:

// Regular Subject

let subject = new Subject(); 

subject.next("b");

subject.subscribe(value => 
  console.log("Subscription got", value); // Subscription wont get 
                                          // anything at this point
);

subject.next("c"); // Subscription got c
subject.next("d"); // Subscription got d

可以使用 subject.asObservable()SubjectBehaviorSubject 创建 observable。

唯一的区别是您不能使用next() 方法将值发送到可观察对象。

在 Angular 服务中,我会将 BehaviorSubject 用于数据服务,因为 Angular 服务通常在组件之前初始化,并且行为主体确保使用该服务的组件接收到最后更新的数据,即使自该组件以来没有新的更新订阅此数据。

【讨论】:

我对常规主题的示例 2 有点困惑。为什么即使在您使用 subject.next("b") 向主题发送值的第二行,订阅也不会得到任何东西? @jmod999 第二个例子是一个常规主题,它在订阅被调用之前接收一个值。在常规主题中,只有在调用订阅后收到的值才会触发订阅。由于 a 是在订阅之前收到的,因此不会发送到订阅。 一个关于这个奇妙解决方案的注释,如果你在一个函数中使用它并返回它,然后返回一个 observable。我在返回主题时遇到了一些问题,这让其他只知道什么是 Observables 的开发人员感到困惑 我在周三接受了 Angular 4 的采访。由于我仍在学习新平台,他问我类似“如果我订阅了一个尚未延迟加载的模块中的可观察对象会发生什么?”,这让我很困惑。我不确定,但他告诉我答案是使用 BSubject - 正是 Bhadoria 先生在上面解释的。答案是使用 BSubject,因为它总是返回最新的值(至少我记得面试官对此的最终评论)。 @bob.mazzo 为什么我需要在这种情况下使用 BSubject? -- 如果我订阅了那个观察者,我不会收到任何东西,因为观察者没有被初始化,所以它不能将数据推送给观察者,如果我使用 BSubject,我也不会收到任何东西,因为同样的原因。在这两种情况下,订阅者都不会收到任何东西,因为它位于尚未初始化的模块中。我说的对吗?【参考方案2】:

Observable:每个 Observer 的结果不同

一个非常非常重要的区别。由于 Observable 只是一个函数,它没有任何状态,所以对于每一个新的 Observer,它都会一次又一次地执行 observable 创建代码。这导致:

代码为每个观察者运行 .如果它是一个 HTTP 调用,它会被每个观察者调用

这会导致严重的错误和效率低下

BehaviorSubject(或Subject)存储观察者详细信息,只运行一次代码并将结果提供给所有观察者。

例如:

JSBin:http://jsbin.com/qowulet/edit?js,console

// --- Observable ---
let randomNumGenerator1 = Rx.Observable.create(observer => 
   observer.next(Math.random());
);

let observer1 = randomNumGenerator1
      .subscribe(num => console.log('observer 1: '+ num));

let observer2 = randomNumGenerator1
      .subscribe(num => console.log('observer 2: '+ num));


// ------ BehaviorSubject/ Subject

let randomNumGenerator2 = new Rx.BehaviorSubject(0);
randomNumGenerator2.next(Math.random());

let observer1Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 1: '+ num));
      
let observer2Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 2: '+ num));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>

输出:

"observer 1: 0.7184075243594013"
"observer 2: 0.41271850211336103"
"observer subject 1: 0.8034263165479893"
"observer subject 2: 0.8034263165479893"

观察使用Observable.create如何为每个观察者创建不同的输出,但BehaviorSubject为所有观察者提供相同的输出。这很重要。


总结其他差异。

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃         Observable                  ┃     BehaviorSubject/Subject         ┃      
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 
┃ Is just a function, no state        ┃ Has state. Stores data in memory    ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Code run for each observer          ┃ Same code run                       ┃
┃                                     ┃ only once for all observers         ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Creates only Observable             ┃Can create and also listen Observable┃
┃ ( data producer alone )             ┃ ( data producer and consumer )      ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Usage: Simple Observable with only  ┃ Usage:                              ┃
┃ one Obeserver.                      ┃ * Store data and modify frequently  ┃
┃                                     ┃ * Multiple observers listen to data ┃
┃                                     ┃ * Proxy between Observable  and     ┃
┃                                     ┃   Observer                          ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

【讨论】:

来自KnockoutJS's ko.observable() 的任何人都会立即看到与Rx.BehaviorSubject 相比Rx.Observable 的更多相似之处 @Skeptor Observable: subscribe 方法总是会触发与观察者关联的 onNext 方法并带来返回值。 BehaviourSubject/Subject: 将始终返回流中的最新值。此处订阅主题的方法不会触发其观察者的 onNext 方法,直到它找到流中的最新值。【参考方案3】:

Observablesubject 都是 observable 的手段,观察者可以跟踪它们。但两者都有一些独特的特点。此外,总共有 3 种类型的主题,它们中的每一种都具有独特的特征。让我们试着去理解它们。

您可以在stackblitz 上找到实际示例。 (您需要检查控制台才能看到实际输出)

Observables

它们很冷:当它们至少有一个观察者时,代码就会被执行。

创建数据副本: Observable 为每个观察者创建数据副本。

单向:观察者无法为可观察者(origin/master)赋值。

Subject

它们很热:即使没有观察者,也会执行代码并广播值。

共享数据:相同的数据在所有观察者之间共享。

双向: Observer 可以为 observable(origin/master) 赋值。

如果正在使用主题,那么您会错过在创建观察者之前广播的所有值。所以来了重播主题

ReplaySubject

它们很热门:即使没有观察者,代码也会被执行并且值会被广播。

共享数据:相同的数据在所有观察者之间共享。

双向: Observer 可以为 observable(origin/master) 赋值。加

重播消息流:无论您何时订阅重播主题,您都会收到所有广播的消息。

在主题和重播主题中,您不能将初始值设置为可观察。那么行为主体

来了

BehaviorSubject

它们很热门:即使没有观察者,代码也会被执行并且值会被广播。

共享数据:相同的数据在所有观察者之间共享。

双向: Observer 可以为 observable(origin/master) 赋值。加

重播消息流:无论您何时订阅重播主题,您都会收到所有广播的消息。

你可以设置初始值:你可以用默认值初始化observable。

【讨论】:

值得一提的是,ReplaySubject 有历史并且可以广播/发出一系列(旧)值。仅当缓冲区设置为 1 时,它的行为类似于 BehaviorSubject 对于 BehaviorSubject 段落“重播消息流”似乎不正确【参考方案4】:

Observable 对象表示基于推送的集合。

Observer 和 Observable 接口为基于推送的通知提供了一种通用机制,也称为观察者设计模式。 Observable 对象代表发送通知的对象(提供者); Observer 对象表示接收它们的类(观察者)。

Subject 类继承了 Observable 和 Observer,从某种意义上说,它既是观察者又是可观察对象。您可以使用一个主题订阅所有观察者,然后将该主题订阅到后端数据源

var subject = new Rx.Subject();

var subscription = subject.subscribe(
    function (x)  console.log('onNext: ' + x); ,
    function (e)  console.log('onError: ' + e.message); ,
    function ()  console.log('onCompleted'); );

subject.onNext(1);
// => onNext: 1

subject.onNext(2);
// => onNext: 2

subject.onCompleted();
// => onCompleted

subscription.dispose();

更多关于https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md

【讨论】:

subscription.dispose() 和subscription.unsubscribe() 有什么区别? @choopage 没有区别。后者是新的方式 应该在主题被释放之前取消订阅,否则订阅会变成垃圾,因为它订阅了一个空值。【参考方案5】:

我在示例中没有看到的一件事是,当您通过 asObservable 将 BehaviorSubject 转换为 Observable 时,它​​会继承在订阅时返回最后一个值的行为。

这是一个棘手的问题,因为库通常会将字段公开为可观察的(即 Angular2 中的 ActivatedRoute 中的参数),但可能在幕后使用 Subject 或 BehaviorSubject。他们使用什么会影响订阅行为。

请看这里http://jsbin.com/ziquxapubo/edit?html,js,console

let A = new Rx.Subject();
let B = new Rx.BehaviorSubject(0);

A.next(1);
B.next(1);

A.asObservable().subscribe(n => console.log('A', n));
B.asObservable().subscribe(n => console.log('B', n));

A.next(2);
B.next(2);

【讨论】:

【参考方案6】:

observable 只允许您订阅,而subject 允许您发布和订阅。

因此,主题允许您的services 同时用作发布者和订阅者。

到目前为止,我还不太擅长Observable,所以我只分享一个Subject的例子。

让我们通过Angular CLI 示例更好地理解。运行以下命令:

npm install -g @angular/cli

ng new angular2-subject

cd angular2-subject

ng serve

app.component.html的内容替换为:

<div *ngIf="message">
  message
</div>

<app-home>
</app-home>

运行命令ng g c components/home生成home组件。将home.component.html的内容替换为:

<input type="text" placeholder="Enter message" #message>
<button type="button" (click)="setMessage(message)" >Send message</button>

#message 是这里的局部变量。将属性message: string; 添加到app.component.ts 的类中。

运行此命令ng g s service/message。这将在src\app\service\message.service.ts 生成服务。提供this service to the app。

Subject 导入MessageService。也添加一个主题。最终代码应如下所示:

import  Injectable  from '@angular/core';
import  Subject  from 'rxjs/Subject';

@Injectable()
export class MessageService 

  public message = new Subject<string>();

  setMessage(value: string) 
    this.message.next(value); //it is publishing this value to all the subscribers that have already subscribed to this message
  

现在,在home.component.ts 中注入此服务并将其实例传递给构造函数。也为app.component.ts 执行此操作。使用此服务实例将#message 的值传递给服务函数setMessage

import  Component  from '@angular/core';
import  MessageService  from '../../service/message.service';

@Component(
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
)
export class HomeComponent 

  constructor(public messageService:MessageService)  

  setMessage(event) 
    console.log(event.value);
    this.messageService.setMessage(event.value);
  

app.component.ts 中,订阅和取消订阅(以防止内存泄漏)Subject

import  Component, OnDestroy  from '@angular/core';
import  MessageService  from './service/message.service';
import  Subscription  from 'rxjs/Subscription';

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html'
)
export class AppComponent 

  message: string;
  subscription: Subscription;

  constructor(public messageService: MessageService)  

  ngOnInit() 
    this.subscription = this.messageService.message.subscribe(
      (message) => 
        this.message = message;
      
    );
  

  ngOnDestroy() 
    this.subscription.unsubscribe();
  

就是这样。

现在,在home.component.html#message 内输入的任何值都应打印到app.component.html 内的message

【讨论】:

为什么是巨型图像?如果它与您的答案没有直接关系,则似乎是投票诱饵。 @ruffin 这只是平均票数的平均答案,请查看我的个人资料。不一定是投票诱饵:D 我早些时候给了你一个赞成票,但你已经回避了为什么图像在那里的问题。它与您的答案没有直接关系。不管你是否有很多代表——if the image isn't directly and specifically elucidatory, I'd request you remove it。 /耸肩 @ruffin 如果它违反了社区的同意,那么它肯定不应该存在!【参考方案7】:

Observables 想象成一个装有流动水的管道,有时水会流动,有时水不会流动。在某些情况下,您实际上可能需要一个始终有水的管道,您可以通过创建一个始终包含水的特殊管道来做到这一点,无论它有多小,我们将此特殊管道称为 BehaviorSubject,如果您恰好是您所在社区的供水供应商,您可以在晚上安然入睡,因为您知道您新安装的管道可以正常工作。

在技术术语中:您可能会遇到 Observable 应该始终在其中具有值的用例,也许您想随着时间的推移捕获输入文本的值,然后您可以创建 BehaviorSubject 的实例来确保这种行为,让我们说:


const firstNameChanges = new BehaviorSubject("<empty>");

// pass value changes.
firstNameChanges.next("Jon");
firstNameChanges.next("Arya");

然后,您可以使用“值”对随时间的变化进行采样。


firstNameChanges.value;

这在您稍后组合 Observables 时会派上用场,通过查看流的类型为 BehaviorSubject,您可以确保 流至少触发一次或至少发出一次信号

【讨论】:

涵盖了很多部分,但你解释的好处是给出了一个易于理解的类比,工藤!!!【参考方案8】:

app.component.ts

behaviourService.setName("behaviour");

behaviour.service.ts

private name = new BehaviorSubject("");
getName = this.name.asObservable();

constructor() 

setName(data) 
    this.name.next(data);

custom.component.ts

behaviourService.subscribe(response=>
    console.log(response);    //output: behaviour
);

【讨论】:

【参考方案9】:

可观察 是一个泛型,

Observables 是多个值随时间推移的惰性集合。

只是一个函数,没有状态

为每个观察者运行代码

行为主题: 一个需要初始值并将其当前值发送给新订阅者的主题。

在技术上是 Observable 的子类型,因为 BehaviorSubject 是具有特定品质的 observable。

有状态。将数据存储在内存中

相同的代码对所有观察者只运行一次

BehaviorSubject 的独特之处如下:

它需要一个初始值,因为它必须始终在订阅时返回一个值,即使它没有收到 next()

订阅时,它返回主题的最后一个值。一个常规的 observable 只有在收到 onnext

时才会触发

在任何时候,您都可以使用 getValue() 方法在不可观察的代码中检索主题的最后一个值。

【讨论】:

【参考方案10】:

BehaviorSubject vs Observable:RxJS 有观察者和可观察者,Rxjs 提供了多个用于数据流的类,其中一个是 BehaviorSubject。

Observables:Observables 是多个值随时间推移的惰性集合。

BehaviorSubject:需要初始值并将其当前值发送给新订阅者的主题。

 // RxJS v6+
import  BehaviorSubject  from 'rxjs';

const subject = new BehaviorSubject(123);

//two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

//two subscribers will get new value => output: 456, 456
subject.next(456);

//new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

//all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

// output: 123, 123, 456, 456, 456, 789, 789, 789

【讨论】:

【参考方案11】:

行为主题

BehaviorSubject 建立在与我们的 ReplaySubject 相同的功能之上,主题喜欢,热,并重放以前的值。

BehaviorSubject 增加了一项功能,您可以给 BehaviorSubject 一个初始值。让我们继续看看这段代码

import  ReplaySubject  from 'rxjs';

const behaviorSubject = new BehaviorSubject(
  'hello initial value from BehaviorSubject'
);

behaviorSubject.subscribe(v => console.log(v));

behaviorSubject.next('hello again from BehaviorSubject');

可观察的

首先,我们将了解创建常规 Observable 的最小 API。有几种方法可以创建 Observable。我们创建 Observable 的方式是实例化类。其他运算符可以简化这一点,但我们希望将实例化步骤与我们不同的 Observable 类型进行比较

import  Observable  from 'rxjs';

const observable = new Observable(observer => 
  setTimeout(() => observer.next('hello from Observable!'), 1000);
);

observable.subscribe(v => console.log(v));

【讨论】:

【参考方案12】:

rxjs 中的主题本质上是一个观察者和可观察者的混合体。 Observer 是我们在值中抛出的东西,observable 是我们可以观察值的东西。

主题默认为热门。 Observables 默认是冷的。这意味着在有人订阅之前,它们不会发出任何值。在我们创建主题的那一刻,我们可以从中发出一个值,即使没有人订阅它,该值也会被发出。 默认情况下,主题是多播。默认情况下,Observable 是单播的,这意味着对于我们拥有的每个不同的观察者,我们必须订阅一个 observable,如果该 observable 发出一个值,该值将为每个订阅者一次流过我们管道内的所有不同运算符.多播意味着所有其他运算符将为每个值运行一次,无论我们拥有多少观察者。 GOTCHA= THE SUBJECT 是多播,但如果您将管道语句链接到它,那将返回一个新的冷单播 observable。

行为主题与主题相同,但也采用初始“种子”值。新订户立即获得最新价值。如果有人订阅了 Behavior 主题,它将立即收到最新的值。因此,行为主题总是有一些价值可以提供给订阅者。

关于行为主题最有用的事情是当我们开始发出网络请求时。想象一下,我们将一些管道内容链接到行为主体,并在管道函数或管道运算符内部,我们最终发出网络请求并获取一些数据。您最终可能希望让其他东西订阅该 observable 并立即获取已经获取的数据。使用行为主体,我们可以轻松实现这种行为。

【讨论】:

以上是关于BehaviorSubject vs Observable?的主要内容,如果未能解决你的问题,请参考以下文章

node_modules 没有导出的成员 'BehaviorSubject'

RxJS - BehaviorSubject,onComplete 未调用

延迟Rxjs BehaviorSubject机制

找不到模块 'rxjs/subject/BehaviorSubject'

BehaviorSubject 添加相同的值

期待一个间谍,但得到了 BehaviorSubject