RxJS `includes` 操作符完全指南:在 Observable 流中检索元素与 fromIndex 起始索引解析
2026/9/20 10:20:18 网站建设 项目流程

RxJSincludes操作符完全指南:在 Observable 流中检索元素与 fromIndex 起始索引解析

【免费下载链接】RxJSThe Reactive Extensions for JavaScript项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS

本文是 RxJS(The Reactive Extensions for JavaScript)Rx.Observable.prototype.includes(searchElement, [fromIndex])操作符的技术详解,聚焦于如何在异步数据流中"从指定起始位置开始查找某个值是否存在"。读完本文,你将掌握includes的参数语义、返回值契约、边界行为(负数索引、InfinityNaN-0),并能结合仓库源码与单元测试理解其底层实现原理,在实际项目中正确选用它替代已弃用的contains

一、includes是什么

includes是一个聚合类(aggregates)操作符,用于判断一个 Observable 序列是否包含指定的元素,并支持通过fromIndex指定搜索的起始位置。它在概念上等价于数组方法Array.prototype.includes,但以响应式的方式工作:它不直接返回布尔值,而是返回一个只发射单个布尔元素的 Observable

该操作符在 关联文档 中定义如下:

Rx.Observable.prototype.includes(searchElement, [fromIndex])

同时文档还声明了一个**已弃用(DEPRECATED)**的别名:

Rx.Observable.prototype.contains(searchElement, [fromIndex]) **DEPRECATED**

注意:从当前仓库源码看,src/core/linq/observable/includes.js 只实现了observableProto.includescontains作为历史遗留别名已被弃用,新代码应统一使用includes

参数说明

参数类型必填说明
searchElementAny要在源序列中定位的值
fromIndexNumber开始搜索的索引位置,未指定时默认为 0

返回值

Observable:一个包含单个元素的 Observable,该元素为布尔值,表示源序列从fromIndex位置起是否包含与searchElement匹配的值。

二、基础用法示例

以下示例完整取自 关联文档,可直接复制运行。

不带起始索引

/* Without an index */ var source = Rx.Observable.of(42) .includes(42); var subscription = source.subscribe( function (x) { console.log('Next: %s', x); }, function (err) { console.log('Error: %s', err); }, function () { console.log('Completed'); }); // => Next: true // => Completed

带起始索引

/* With an index */ var source = Rx.Observable.of(1,2,3) .includes(2, 1); var subscription = source.subscribe( function (x) { console.log('Next: %s', x); }, function (err) { console.log('Error: %s', err); }, function () { console.log('Completed'); }); // => Next: true // => Completed

第二个例子中,源序列为1, 2, 3,从索引1(即元素2)开始搜索,命中2,因此发射true并立即完成。

三、源码实现剖析

includes的完整实现位于 src/core/linq/observable/includes.js,整体采用"自定义 Observable + 自定义 Observer"的经典 RxJS 4 架构。代码结构清晰,由三个部分组成:

1.IncludesObservable:参数归一化与负索引短路

var IncludesObservable = (function (__super__) { inherits(IncludesObservable, __super__); function IncludesObservable(source, elem, idx) { var n = +idx || 0; Math.abs(n) === Infinity && (n = 0); this.source = source; this._elem = elem; this._n = n; __super__.call(this); } IncludesObservable.prototype.subscribeCore = function (o) { if (this._n < 0) { o.onNext(false); o.onCompleted(); return disposableEmpty; } return this.source.subscribe(new IncludesObserver(o, this._elem, this._n)); }; return IncludesObservable; }(ObservableBase));

构造函数中的fromIndex归一化逻辑值得注意:

  • var n = +idx || 0:使用一元+将索引强制转为数字;当fromIndex未传入(undefined)时,+undefinedNaNNaN || 0回退为0,这就是"未指定时默认为 0"的底层来源;
  • Math.abs(n) === Infinity && (n = 0):当传入Infinity-Infinity时,将其归一化为0

subscribeCore中的短路逻辑:只要fromIndex < 0,就直接发射false并完成,根本不会订阅源序列。这意味着负数索引始终返回false,这一点与原生数组includes的行为不同(数组的includes会将负数索引换算为length + fromIndex)。

2.IncludesObserver:逐元素匹配与提前终止

var IncludesObserver = (function (__super__) { inherits(IncludesObserver, __super__); function IncludesObserver(o, elem, n) { this._o = o; this._elem = elem; this._n = n; this._i = 0; __super__.call(this); } IncludesObserver.prototype.next = function (x) { if (this._i++ >= this._n && comparer(x, this._elem)) { this._o.onNext(true); this._o.onCompleted(); } }; IncludesObserver.prototype.error = function (e) { this._o.onError(e); }; IncludesObserver.prototype.completed = function () { this._o.onNext(false); this._o.onCompleted(); }; return IncludesObserver; }(AbstractObserver));

匹配逻辑由三行核心代码构成:

  • 观察者内部维护计数器_i(从 0 递增),this._i++ >= this._n保证只有索引达到fromIndex之后的元素才参与比较;
  • 一旦命中(comparer返回true),立即发射true并调用onCompleted()提前终止——源序列中后续元素不会再产生任何输出;
  • 若源序列正常结束仍未命中,completed回调发射false并完成;
  • 源序列出错时,error回调将错误原样透传给下游,不做吞掉或转换。

3. 比较器comparer:处理0-0NaN

function comparer(a, b) { return (a === 0 && b === 0) || (a === b || (isNaN(a) && isNaN(b))); }

这里的相等语义与===有两点关键差异:

  • NaN等于NaNNaN === NaN在 JavaScript 中为false,但includes通过isNaN(a) && isNaN(b)显式让两个NaN视为相等;
  • 0+0-0统一处理(a === 0 && b === 0)分支确保0+0-0之间的任意组合都被判定为相等。

这意味着你可以用includes(NaN)在流中查找NaN值,这在原生===语义下是做不到的。

四、fromIndex边界行为速查

结合源码(src/core/linq/observable/includes.js)与单元测试(tests/observable/includes.js、src/modular/test/includes.js),fromIndex的边界行为可归纳如下:

fromIndex取值行为测试用例
未指定(undefined+undefined \|\| 0归一为 0,从开头搜索includes return positive
0从索引 0 开始搜索includes fromIndex zero
正整数(如1跳过前n个元素,从第n个索引起搜索;命中即true,否则序列结束时为falseincludes fromIndex greater than zero hits / misses
负数(如-1直接发射false并完成,不订阅源序列includes fromIndex less than zero
Infinity/-Infinity归一化为 0,等价于从开头搜索includes fromIndex Infinity
NaN等无法转数字的值+idx \|\| 0回退为 0从实现推断

需要特别强调的是Infinity被归一为 0这一行为:测试includes fromIndex Infinity中,序列在 210ms 发射2,调用xs.includes(2, Infinity)仍期望onNext(210, true)——因为索引被重置为 0,所以能够命中。

五、单元测试验证:行为契约一览

tests/observable/includes.js使用 RxJS 的TestSchedulerReactiveTestincludes进行了完整覆盖,每个用例都精确断言了时间点与值,是理解该操作符行为契约的最佳参考:

  • 空序列onCompleted(250)的源序列调用includes(42),期望onNext(250, false)+onCompleted(250)——空序列返回false
  • 命中即完成:源在 210ms 发射2includes(2)期望onNext(210, true)+onCompleted(210)发射结果的时间点与被命中元素的时间点一致,且不再等待源序列的onCompleted(250),证明命中后立即提前终止;
  • 未命中:源发射2但搜索-2,期望等到源序列结束时(250ms)才输出false
  • 错误透传:源在 210ms 抛错,includes期望onError(210, error),错误被原样传递;
  • 永不完结序列never):includes不会产生任何输出(results.messages.assertEqual()为空),符合"永不完结则结果永不出现"的响应式语义;
  • fromIndex大于 0 且不命中:源2,3,4,5在 250ms 完成,includes(2, 1)期望onNext(250, false)——2位于索引 0,被fromIndex = 1跳过;
  • fromIndex大于 0 且无终点:同一序列但无onCompletedincludes(2, 1)期望无任何输出
  • -0/+0/NaN匹配includes(0)对源中的-0+0均返回trueincludes(NaN)对源中的NaN返回true

模块化版本在 src/modular/test/includes.js 中提供了几乎一致的用例(使用 tape 风格),两套测试共同锁定了该操作符的语义。

六、模块化版本与分发渠道

模块化实现

除核心单文件版本外,仓库还提供了 webpack 模块化实现 src/modular/observable/includes.js,采用module.exports导出,并在 src/modular/index.js 中注册:

includes: require('./observable/includes'),

模块化版本与核心版本的实现完全一致(同样的索引归一化、同样的比较器、同样的提前终止逻辑),只是换用了 CommonJS 依赖注入风格。

可用分发渠道

根据 关联文档 的 Location 章节,includes属于聚合类操作符,随以下构建产物分发:

  • Dist 文件rx.all.jsrx.all.compat.jsrx.aggregates.js(依赖rx.js/rx.compat.js/rx.lite.js/rx.lite.compat.js作为前置);
  • NPM 包rx
  • NuGet 包RxJS-AllRxJS-Aggregates,对应仓库中的 nuget/RxJS-All 与 nuget/RxJS-Aggregates;
  • 仓库模块目录:modules/rx-lite-aggregates 与 modules/rx-lite-aggregates-compat 中同样包含了聚合类操作符的独立构建。

完整的聚合类操作符清单可参阅 doc/libraries/main/rx.aggregates.md,对应的浏览器测试页为 tests/rx.aggregates.html。

七、使用建议与注意事项

  1. 优先使用includes而非containscontains已被文档标记为 DEPRECATED,当前源码也仅保留includes实现;
  2. 结果是一个 Observable,不是布尔值includes返回的是发射单个布尔元素的流,需要subscribe才能获得结果,这也意味着它天然适用于与switchmerge等其他响应式操作符的组合场景;
  3. 命中即提前终止:如果只需判断"是否存在",includes不会等待源序列结束,适合用于长流甚至无限流的前置判断(但若元素位于流末尾且流不完结,则永远不会得出结果,参见includes never测试);
  4. 负索引语义与原生数组不同:原生Array.prototype.includes支持array.length + fromIndex式的负数换算,而 RxJS 的includes对负数索引直接返回false,移植代码时需留意;
  5. NaN可以匹配:得益于自定义比较器,includes(NaN)能够命中流中的NaN,这在依赖===的普通比较中无法实现。

【免费下载链接】RxJSThe Reactive Extensions for JavaScript项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询