RxJS 简介:可观察对象、观察者与操作符

对于响应式编程来说,RxJS 是一个不可思议的工具。今天我们将深入探讨什么是 Observable(可观察对象)和 observer(观察者),然后了解如何创建自己的 operator(操作符)。

如果你之前用过 RxJS,想了解它的内部工作原理,以及 Observable、operator 是如何运作的,这篇文章将很适合你阅读。

什么是 Observable(可观察对象)?

可观察对象其实就是一个比较特别的函数,它接受一个“观察者”(observer)对象作为参数(在这个观察者对象中有 “next”、“error”、“complete”等方法),以及它会返回一种解除与观察者关系的逻辑。例如我们自己实现的时候会使用一个简单的 “unsubscribe” 函数来实现退订功能(即解除与观察者绑定关系的逻辑)。而在 RxJS 中, 它是一个包含 unsubsribe 方法的订阅对象(Subscription)。

可观察对象会创建观察者对象(稍后我们将详细介绍它),并将它和我们希望获取数据值的“东西”连接起来。这个“东西”就是生产者(producer),它可能来自于 click 或者 input之类的 DOM 事件,是数据值的来源。当然,它也可以是一些更复杂的情况,比如通过 HTTP 与服务器交流的事件。

我们稍后将要自己写一个可观察对象,以便更好地理解它!在此之前,让我们先看看一个订阅对象的例子:

const node = document.querySelector('input[type=text]');

const input$ = Rx.Observable.fromEvent(node, 'input');

input$.subscribe({
next: (event) => console.log(`你刚刚输入了 ${event.target.value}!`),
error: (err) => console.log(`Oops... ${err}`),
complete: () => console.log(`完成!`)
});

这个例子使用了一个 <input type="text"> 节点,并将其传入 Rx.Observable.fromEvent() 中。当我们触发指定的事件名时,它将会返回一个输入的 Event 的可观察对象。(因此我们在 console.log 中用 ${event.target.value} 可以获取输入值)

当输入事件被触发的时候,可观察对象会将它的值传给观察者。

什么是 Observer(观察者)?

观察者相当容易理解。在前面的例子中,我们传入 .subscribe() 中的对象字面量就是观察者(订阅对象将会调用我们的可观察对象)。

.subscribe(next, error, complete) 也是一种合法的语法,但是我们现在研究的是对象字面量的情况。

当一个可观察对象产生数据值的时候,它会通知观察者,当新的值被成功捕获的时候调用 .next(),发生错误的时候调用 .error()

当我们订阅一个可观察对象的时候,它会持续不断地将值传递给观察者,直到发生以下两件事:一种是生产者告知没有更多的值需要传递了,这种情况它会调用观察者的 .complete();一种是我们(“消费者”)对之后的值不再感兴趣,决定取消订阅(unsubsribe)。

如果我们想要对可观察对象传来的值进行组成构建(compose),那么在值传达最终的 .subscribe() 代码块之前,需要经过一连串的可观察对象(也就是操作符)处理。这个一连串的“链”也就是我们所说的可观察对象序列。链中的每个操作符都会返回一个新的可观察对象,让我们的序列能够持续进行下去——这也就是我们所熟知的“流”。

什么是 Operator(操作符)?

我们前面提到,可观察对象能够进行链式调用,也就是说我们可以像这样写代码:

const input$ = Rx.Observable.fromEvent(node, 'input')
.map(event => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => {
// use the `value`
});

这段代码做了下面一系列事情:

  • 我们先假定用户输入了一个“a”
  • 可观察对象将会对这个输入事件作出反应,将值传给下一个观察者
  • “a”被传给了订阅了我们初始可观察对象的 .map()
  • .map() 会返回一个 event.target.value 的新可观察对象,然后调用它观察者对象中的 .next()
  • .next() 将会调用订阅了 .map() 的 .filter(),并将 .map() 处理后的值传递给它
  • .filter() 将会返回另一个可观察对象,.filter() 过滤后留下 .length 大于等于 2 的值,并将其传给 .next()
  • 我们通过 .subscribe() 获得了最终的数据值

这短短的几行代码做了这么多的事!如果你还觉得弄不清,只需要记住:

每当返回一个新的可观察对象,都会有一个新的观察者挂载到前一个可观察对象上,这样就能通过观察者的“流”进行传值,对观察者生产的值进行处理,然后调用 .next() 方法将处理后的值传递给下一个观察者。

简单来说,操作符将会不断地依次返回新的可观察对象,让我们的流能够持续进行。作为用户而言,我们不需要关心什么时候、什么情况下需要创建与使用可观察对象与观察者,我们只需要用我们的订阅对象进行链式调用就行了。

创建我们自己的 Observable(可观察对象)

现在,让我们开始写自己的可观察对象的实现吧。尽管它不会像 Rx 的实现那么高级,但我们还是对完善它充满信心。

Observable 构造器

首先,我们需要创建一个 Observable 构造函数,此构造函数接受且仅接受 subscribe 函数作为其唯一的参数。每个 Observable 实例都存储 subscribe 属性,稍后可以由观察者对象调用它:

function Observable(subscribe) {
this.subscribe = subscribe;
}

每个分配给 this.subscribe 的 subscribe 回调都将会被我们或者其它的可观察对象调用。这样我们下面做的事情就有意义了。

Observer 示例

在深入探讨实际情况之前,我们先看一看基础的例子。

现在我们已经配好了可观察对象函数,可以调用我们的观察者,将 1 这个值传给它并订阅它:

const one$ = new Observable((observer) => {
observer.next(1);
observer.complete();
}); one$.subscribe({
next: (value) => console.log(value) // 1
});

我们订阅了 Observable 实例,将我们的 observer(对象字面量)传入构造器中(之后它会被分配给 this.subscribe)。

Observable.fromEvent

现在我们已经完成了创建自己的 Observable 的基础步骤。下一步是为 Observable 添加 static 方法:

Observable.fromEvent = (element, name) => {

};

我们将像使用 RxJS 一样使用我们的 Observable:

const node = document.querySelector('input');

const input$ = Observable.fromEvent(node, 'input');

这意味着我们需要返回一个新的 Observable,然后将函数作为参数传递给它:

Observable.fromEvent = (element, name) => {
return new Observable((observer) => { });
};

这段代码将我们的函数传入了构造器中的 this.subscribe。接下来,我们需要将事件监听设置好:

Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
element.addEventListener(name, (event) => {}, false);
});
};

那么这个 observer 参数是什么呢?它又是从哪里来的呢?

这个 observer 其实就是携带 nexterrorcomplete 的对象字面量。

这块其实很有意思。observer 在 .subscribe() 被调用之前都不会被传递,因此 addEventListener 在 Observable 被“订阅”之前都不会被执行。

一旦调用 subscribe,也就会调用 Observable 构造器内的 this.subscribe 。它将会调用我们传入 new Observable(callback) 的 callback,同时也会依次将值传给我们的观察者。这样,当 Observable 做完一件事的时候,它就会用更新过的值调用我们观察者中的 .next() 方法。

那么之后呢?我们已经得到了初始化好的事件监听器,但是还没有调用 .next()。下面完成它:

Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
element.addEventListener(name, (event) => {
observer.next(event);
}, false);
});
};

我们都知道,可观察对象在被销毁前需要一个“处理后事”的函数,在我们这个例子中,我们需要移除事件监听:

Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
const callback = (event) => observer.next(event);
element.addEventListener(name, callback, false);
return () => element.removeEventListener(name, callback, false);
});
};

因为这个 Observable 还在处理 DOM API 和事件,因此我们还不会去调用 .complete()。这样在技术上就有无限的可用性。

试一试吧!下面是我们已经写好的完整代码:

const node = document.querySelector('input');
const p = document.querySelector('p'); function Observable(subscribe) {
this.subscribe = subscribe;
} Observable.fromEvent = (element, name) => {
return new Observable((observer) => {
const callback = (event) => observer.next(event);
element.addEventListener(name, callback, false);
return () => element.removeEventListener(name, callback, false);
});
}; const input$ = Observable.fromEvent(node, 'input'); const unsubscribe = input$.subscribe({
next: (event) => {
p.innerHTML = event.target.value;
}
}); // 5 秒之后自动取消订阅
setTimeout(unsubscribe, 5000);

在线示例:

创造我们自己的 Operator(操作符)

在我们理解了可观察对象与观察者对象的概念之后,我们可以更轻松地去创造我们自己的操作符了。我们在 Observable 对象原型中加上一个新的方法:

Observable.prototype.map=function(mapFn){

};

这个方法将会像 JavaScript 中的 Array.prototype.map 一样使用,不过它可以对任何值用:

const input$ = Observable.fromEvent(node, 'input')
.map(event => event.target.value);

所以我们要取得回调函数,并调用它,返回我们期望得到的数据。在这之前,我们需要拿到流中最新的数据值。

下面该做什么就比较明了了,我们要得到调用了这个 .map() 操作符的 Observable 实例的引用入口。我们是在原型链上编程,因此可以直接这么做:

Observable.prototype.map = function (mapFn) {
const input = this;
};

找找乐子吧!现在我们可以在返回的 Obeservable 中调用 subscribe:

Observable.prototype.map = function (mapFn) {
const input = this;
return new Observable((observer) => {
return input.subscribe();
});
};

我们要返回 input.subscribe() ,因为在我们退订的时候,非订阅对象将会顺着链一直转下去,解除每个 Observable 的订阅。

这个订阅对象将允许我们把之前 Observable.fromEvent 传来的值传递下去,因为它返回了构造器中含有 subscribe 原型的新的 Observable 对象。我们可以轻松地订阅它对数据值做出的任何更新!最后,完成通过 map 调用我们的 mapFn() 的功能:

Observable.prototype.map = function (mapFn) {
const input = this;
return new Observable((observer) => {
return input.subscribe({
next: (value) => observer.next(mapFn(value)),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
};

现在我们可以进行链式调用了!

const input$ = Observable.fromEvent(node, 'input')
.map(event => event.target.value); input$.subscribe({
next: (value) => {
p.innerHTML = value;
}
});

注意到最后一个 .subscribe() 不再和之前一样传入 Event 对象,而是传入了一个 value 了吗?这说明你成功地创建了一个可观察对象流。

再试试:

希望这篇文章对你来说还算有趣~:)

RxJS 简介:可观察对象、观察者与操作符的更多相关文章

  1. [译]Rxjs&Angular-退订可观察对象的n中方式

    原文/出处: RxJS & Angular - Unsubscribe Like a Pro 在angular项目中我们不可避免的要使用RxJS可观察对象(Observables)来进行订阅( ...

  2. Angular的Observable可观察对象(转)

    原文:https://blog.csdn.net/qq_34414916/article/details/85194098 Observable 在开始讲服务之前,我们先来看一下一个新东西——Obse ...

  3. Angular2 Observable 可观察对象

    可观察对象支持在应用中的发布者和订阅者之间传递消息.在需要进行事件处理,异步编程和处理多值的时候,可观察对象相对其他技术有显著的优点. 可观察对象是声明式的 —— 也就是说,虽然你定义了一个用于发布值 ...

  4. JavaScript之面向对象学习二(原型属性对象与in操作符)获取对象中所有属性的方法

    1.原型属性对象于in操作符之in单独使用 有两种方式使用in操作符:单独使用和在for-in循环中使用.在单独使用中,代码如下: function Person(){ } Person.protot ...

  5. ngx通讯之可观察对象实现

    1.公共服务 //test.service.ts import {Injectable} from '@angular/core'; import {Subject} from 'rxjs/Subje ...

  6. wex5 教程 之 图文讲解 可观察对象的集群应用与绑定技术

    一 前言: wex5官方教程里,开篇即以一个input输入,output即时输出的例子,为我们展现了一个概念:可观察对象.在以后我的项目开发中,将大量运用可观察对象. 那么,问题来了: 1. 可观察对 ...

  7. JSON数据表示格式简介(JavaScript对象表示法)

    [1] JSON简介    > JSON全称 JavaScript Object Notation    > 类似于JS中对象的创建的方法    > JSON和XML一样,都是一种表 ...

  8. Object.observe() 观察对象

    这个对象方法可以用来异步观察对javascript对象的改动: // Let's say we have a model with data var model = {};   // Which we ...

  9. RxJS简介

    函数式编程 1.声明式(Declarativ) 和声明式相对应的编程⽅式叫做命令式编程(ImperativeProgramming),命令式编程也是最常见的⼀种编程⽅式. //命令式编程: funct ...

随机推荐

  1. Linux信号(signal)机制【转】

    转自:http://gityuan.com/2015/12/20/signal/ 信号(signal)是一种软中断,信号机制是进程间通信的一种方式,采用异步通信方式 一.信号类型 Linux系统共定义 ...

  2. 6 个 Linux 运维典型问题,大牛的分析解决思路在这里 【转】

    作为一名合格的 Linux 运维工程师,一定要有一套清晰.明确的解决故障思路,当问题出现时,才能迅速定位.解决问题,这里给出一个处理问题的一般思路: 重视报错提示信息:每个错误的出现,都是给出错误提示 ...

  3. C#使用redis学习笔记

    1.官网:http://redis.io/(英)  http://www.redis.cn/(中) 2.下载:https://github.com/dmajkic/redis/downloads(Wi ...

  4. JavaScript——创建对象

    <script type="text/javascript"> //声明变量的首字母是小写 //1.对象字面量 /*var person = { name:" ...

  5. 使用管道和cronolog切割日志

    安装cronolog git clone https://github.com/fordmason/cronolog ./configure make && make install ...

  6. PyTorch-Kaldi 语音识别工具包

    翻译:  https://arxiv.org/pdf/1811.07453.pdf ABSTRACT 开源软件的可用性在语音识别和深度学习的普及中发挥了重要作用.例如,Kaldi 现在是用于开发最先进 ...

  7. ES6+Webpack 下使用 Web Worker

    大家都知道 HTML 5 新增了很多 API,其中就包括 Web Worker,在普通的 js 文件上使用 ES5 编写相关代码应该是完全没有问题了,只需要在支持 H5 的浏览器上就能跑起来. 那如果 ...

  8. 006.KVM虚机克隆

    一 KVM宿主机内克隆 1.1 查看虚拟机配置 [root@kvm-host ~]# cat /etc/libvirt/qemu/vm01-centos6.8.xml ………… [root@kvm-h ...

  9. [转]C++ STL list的初始化、添加、遍历、插入、删除、查找、排序、释放

    list是C++标准模版库(STL,Standard Template Library)中的部分内容.实际上,list容器就是一个双向链表,可以高效地进行插入删除元素. 使用list容器之前必须加上S ...

  10. JAVAEE——SpringBoot入门:简介、微服务、环境准备、helloworld与探究、快速构建项目

    一.Spring Boot 入门 1.Spring Boot 简介 简化Spring应用开发的一个框架: 整个Spring技术栈的一个大整合: J2EE开发的一站式解决方案: 2.微服务 2014,m ...