RxJS 全面解析
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
作者:霍世杰 @阿杰 前言打开此文的小伙伴想必对 RxJS 已经有了或多或少的了解,如果没有倒也无妨,因为下面会从零开始讲起;如果你带着几个问题来翻阅,本人也希望此文可以带你找到答案。 温馨提示:文章内容较长,建议收藏反复观看。 概览从我个人的学习 RxJS 的历程来看,最开始是“照猫画虎”能够基本使用,随后是研究部分操作符和使用场景,最后了解产生背景、设计思想以及实现原理。在这期间有过很多疑问,也曾从不同角度理解 RxJS,最终总结了认为比较系统的知识图谱(下图)。 深入理解 RxJS大“道”——响应式编程全面理解一个事物,追溯其历史是一种好的方式,RxJS 的起源需要追溯到响应式编程(RP),后续产生了一系列基于响应式编程范式的语言扩展(Rx,RxJS 就是其中之一),请看历史简谱(左向右延续)。 何为响应式 响应式是学习 RxJS 必须要理解的概念,本人用了大量的文字来解释,如果您已经深刻理解,可直接跳过。如果您是第一次接触这个名词,也不要先给自己心里暗示它有多么的高深和难以理解,也许你天天在使用。 一个例子 为了避免上来就接触晦涩的概念,先来举个例子:博客平台关注功能。话说你偶然浏览到阿杰的文章,觉得写的很赞,于是你关注了他的博客账号,以便不会错过之后的干货,在以后的日子里阿杰每发布一篇文章博客平台都会给你推送一条消息,提醒你来给他点点赞,假设博客平台没有关注的功能,那么你需要想知道他的最新动态就只能打开他的个人主页查看文章列表来确认,也许稍不留意就会错过他的文章。这个例子出现了粉丝关注博主、博主发布博客、平台自动推送给粉丝消息、给文章点赞,这就形成了响应式闭环,平台在观察到博主粉丝只需要关注一下就能收到博主以后的动态,这就是响应式带来的好处。 另一个例子 再举一个贴近我们开发的例子:假设有一个更新某用户密码的需求,A 同事负责调用更新逻辑并在更新后执行其他任务(比如提醒用户更新成功或失败),B 同事负责具体更新密码的逻辑,下图描述了完成整个任务的流程:
上述的每个环节都有可能是异步耗时任务,比如用户的真实性是第三方平台验证的,入库的过程中网络非常慢,再比如......等等,诸如此类的各种不确定性,这对于 B 同事做后续任务就有了一个关键性条件,确定/等待更新结果,这种情况有一种做法是:定期轮询重试,B 每隔一段时间执行一次,直到确定 A 已经修改成功,再去执行后续操作。逻辑中定时 A 逻辑结束这种做法这种做法明显有一个弊端是执行多次,对于 B 显然不是好的做法,好的做法是:B 的更新逻辑执行完后通知 A,甚至 B 可以先把更新后的事准备好,让 A 决定后续逻辑的执行时机。 流程如图示:订阅/执行更新逻辑、更新逻辑结束、将结果通知调用者、执行后续逻辑。这就是响应式的做法,它带来的好处是:当更新结果发生变化时自动通知调用者,而不用轮询重试。 了解响应式宣言 相信你已经明白了响应式,并能发现生活/工作中到处可见,下面了解一下设计响应式模块/系统遵循的原则:
响应式编程 下面我们正式的介绍响应式编程: 响应式编程,Reactive Programing,又称反应式编程,简称 RP,是一种以传播数据流(数据流概念戳这里 )的编程范式。 响应式编程或反应式编程(英语:Reactive programming)是一种面向数据流和变化传播的声明式编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 优势:
核心思想:从传统的调用方“拉”数据的思维模式转变为被调用方“推”数据的思维模式。 JS 异步编程史 众所周知,JS 执行环境是单线程的,在事件监听,异步的处理,响应式编程毋庸置疑是其中的一大主力。 Callback 时代 回调函数延续至今,JS 的运用高阶函数巧妙地将异步后的逻辑进行托管,以事件驱动的方式来解决异步编程,但它有一个“臭名昭著”的问题:回调嵌套,耦合度高。本来很简单的逻辑但为了控制执行流程却不得不写大量的代码,当时产生了一些知名的库:async、bluebrid,它们封装和处理了嵌套问题,暴露出更为简单好用的 API,额外还可以优雅地处理流程控制相关场景,但所做的只是划分了逻辑,依旧没有解决代码臃肿的问题。 Promise 时代 ES6 纳入 Promise 之后可谓一大喜讯,因为它解决了回调嵌套的问题,虽然它只是回调的语法糖,但在处理流程和捕获错误(外层处理)已经非常的优雅了,但它的弊端是:无法监听和打断 Promise 的状态。这意味着一旦声明它会立即执行并修改它的执行状态,这源于它的实现。 Generator Generator 是处于 Promise 和 Async/await 之间的产物,它给我们带来了写异步语法像写同步一般,只需在函数前加 * 修饰,这样就可以在函数内部使用一个 yield 关键字返回结果,类似于 await ,但它也并非完美,不然也不会有后面的 Async/await 了,它的主要问题是流程管理不方便(迭代器模式实现,主动调 next 执行器流转游标)。 Async/await Async/await 是 Generator 语法糖,既保留了语法上的优势,也解决了 Generator 每步要调一下 next 执行器的弊端,是现阶段的最佳方案,就是得吐槽一下 Top-level await 到 ES2022 才出现。 其中 Generator 和 Async/await 在异步编程是以等待的方式处理。 ReactiveX 业界一致认为正统的响应式实现/扩展是 ReactiveX 系列。 ReactiveX,简称 Rx,是基于响应式的扩展,是各种语言实现的一个统称,除了我们所知道的 RxJS,还有 RxJava、http://Rx.NET、RxKotlin、RxPHP.....它最早是由微软提出并引入到 .NET 平台中,随后 ES6 也引入了类似的技术。 它扩展了观察者模式,以支持数据序列和/或事件,并添加了操作符,允许您以声明的方式将序列组合在一起,同时抽象出诸如低级线程、同步、线程安全、并发数据结构和非阻塞I/O等问题。 RxJS RxJS 全称 Reactive Extensions for JavaScript,翻译过来是 Javascript 的响应式扩展,它是一个采用流来处理异步和事件的工具库,简单来说 Rx(JS) = Observables + Operator + Scheduler。 擅长做的事:
最大的优势:异步事件的抽象,这意味着可以把很多事统一起来当作一种方式处理,从而让问题变得更简单,同时也降低了学习成本。 注意:RxJS 擅长做异步的事,不代表不可以做同步或不擅长同步的事。 RxJS 在 Angular 中的应用 RxJS 在 Angular 中及其重要,很多核心模块都是由 RxJS 实现的,比如:
更多: https://angular.io/guide/observables-in-angular RxJS 核心概念—— ObservablesRxJS 中的 Observables 系列是围绕观察者模式来实现的,基本角色:
ObservableObserveable 是观察者模式中的被观察者,它维护一段执行函数,提供了惰性执行的能力(subscribe)。 核心函数
RxJS 中 Observeable 是一等公民,将一切问题都转化为 Observable 去处理。转换的操作符有 基本使用 源码实现 本人写(抽取)了一套 RxJS Observable 源码中的核心实现 Observable 与 Promise用过两者的同学可能会有疑问为什么采用 Observable 而不直接用 Promise 或 Async/await,这两者在业界也常常用来做对比。 它们关键性的不同点:
总的来说,Promise 可读性更优,Observable 从使用场景更为全面。 两者的相互转换 在既使用了 RxJS 又引用了用 Promise 封装的库时,两者相互转换是容易碰到的问题,RxJS 提供了两者转换的函数。 Promise 转 Observable from 或 fromPromise(弃用) 操作符
Observable 转 Promise
Subscriber/ObserverSubscriber/Observer 是观察者模式中的观察者/消费者,它用来消费/执行 Observable 创建的函数。 核心能力
实现 白话描述:
工作流程 Subscription上面的 Observable 和 Observer 已经完成了观察者模式的核心能力,但是引发的一个问题是,每次执行一个流创建一个 Observable,这可能会创建多个对象(尤其是大量使用操作符时,会创建多个 Observable 对象,这个我们后面再说),此时需要外部去销毁此对象,不然会造成内存泄露。 为了解决这个问题,所以产生了一个 Subscription 的对象,Subscription 是表示可清理资源的对象,它是由 Observable 执行之后产生的。 核心能力
注意:调用 使用 实现 白话描述:
完整工作流程 Subject上述的 Observable 归根到底就是一个惰性执行的过程,当遇到以下两种情况就显得偏弱:
基于这两个方面,所以产生了 Subject,Subject 是一个特殊的 Observable,更像一个 EventEmitter,它既可以是被观察者/生产者也可以是观察者/消费者。 优势
场景 消息传递或广播。 与 Observable 的区别
重点解释一下消费策略和消费时机两块: 冷数据流:可以订阅任意时间的数据流。 热数据流:只给已订阅的消费者发送消息,定阅之前的消费者,不会收到消息。 用一个示例来演示: 工作原理 PS:忘记了该图出自哪篇文章,画的挺不错的,这里直接引用了,如有侵权,还望联系作者。 源码实现
其他 Subject
操作符(Operator)由于篇幅问题,本节并不会细化到讲每个操作符 理解操作符Operator 本质上是一个纯函数 (pure function),它接收一个 Observable 作为输入,并生成一个新的 Observable 作为输出。
遵循的小道迭代器模式和集合的函数式编程模式以及管道思想(pipeable) 操作符的实现以及使用均依照函数式的编程范式,Functional Programing,简称 FP,函数式编程范式,它的思维就是一切用函数表达和解决问题,避免用命令式。 优点:
更多详细看这篇 不完全指南 pipe 管道,用来承载数据流的容器,相信大家一定用过 Lodash 的chain,原生 js 数组,NodeJS 开发者 也许还知道 async/bluebird 的 waterfall,Mongodb 的 pipe,它们都遵循管道思想,最直接的好处是链式调用,还可以用来划分逻辑,在异步的场景中还可以做流程控制(串行、并行、竞速等等)。 为什么要有操作符?遵循符合响应式宣言,单向线性的通讯或传输数据,pipe 可以降低耦合度以便于阅读和维护,把复杂的问题分解成多个简单的问题,最后在组合起来。 操作符与数据流在 RxJS 的世界解决问题的方式是抽象为数据流,整个闭环是围绕数据流进行的,所以我们再来理解一下数据流:流,可以把数据可以想像成现实中的水流,河流,流有上游、下游每个阶段处理不同的事情,在这过程避免不了要操作流,比如合并、流程控制、频率控制等等,所以操作符就扮演了此角色。 生命周期:创建流(create、new、创建类操作符)——> 执行流(subscribe) ——> 销毁流(unsubscribe) 分类工作原理迭代器模式:当多个操作符时,组合成多个可迭代对象的集合,执行时依次调用 next 函数。 源码实现
如图 操作符转换 Array 源码
创建自定义操作符方式一
方式二:基于 lift
lift 源码 阅读弹珠/大理石图学会阅读弹珠图是快速理解 Rx 操作符的手段之一,有些操作符需要描述时间流逝以及序列,所以弹珠图有很多的标识和符号,如下图。 这里有几个用来理解大理石图的网站: 学习参考
调度器(Scheduler)何为调度器也许你在使用操作符的过程中从未在意过它,但它在 Rx 起着至关重要的作用,在异步中如何调度异步任务是很复杂的事情(尤其是以线程为核心处理异步任务的语言),很庆幸的是我们用使用的 JS ,所以不需要过多的关注线程问题,更友好的是大多数操作符默认帮开发者选中了合适的调度模式(下文会讲到),以至于我们从忽略了它,但无论如何我们都应该对调度器有基本的了解。 调度器, 官方定义:
种类/模式
示例下面我们举例略窥一下各个模式的表现。 null/undefined/sync
asap
从结果示,from 的数据的输出顺序是在 console.log(同步代码)之后,promise.then 之前的。 async
结果示,from 数据输出顺序是在 console.log(同步代码)和 Promise.then 之后的。 工作原理Scheduler 工作原理可以类比 JS 中的调用栈和事件循环,从实现上 使用原则/策略RxJS Scheduler 的原则是:尽量减少并发运行。
支持调度器的操作符
OK,关于调度器我们先了解到这里。 最后至此,RxJS 内容已经讲解完毕,文中概念较多,若大家都能够理解,就可以对 RxJS 的认知拉到同一个维度,后续需要做的就是玩转各种操作符,解决实际问题,学以致用才可达到真正的精通。 最后如果觉得文章不错,点个赞再走吧! 附文中完整代码与示例: https://github.com/aaaaaajie/simple-rxjs 推荐阅读参考该文章在 2024/11/12 11:22:52 编辑过 |
关键字查询
相关文章
正在查询... |