RxJS tap操作符‘可修改流经对象’含义及相关疑问
一、官方提示语的含义
RxJS官方文档中关于tap操作符有这样的提示:
"Be careful! You can mutate objects as they pass through the tap operator's handlers."
这句话的核心是提醒开发者:tap操作符的回调函数可以修改流中传递的引用类型对象的内部状态,且这种修改会影响到所有持有该对象引用的地方。
你第一次测试用了字符串类型(不可变类型):
obs3 = new Observable<string>((observer) => { console.log('Observable 3 starts') observer.next('1'); observer.next('2'); observer.next('3'); observer.next('4'); observer.next('5'); }) ngOnInit() { this.obs3.pipe(tap(val=>val+1)).subscribe( val=>{console.log(val);} ); }
输出是1、2、3、4、5,原因是字符串是JavaScript中的不可变类型,val+1会创建一个新字符串,但tap不会把这个新值替换到流中——tap的本质是执行副作用但不改变流的内容,所以流里依然是原来的字符串值。
但如果流中传递的是对象、数组这类引用类型,情况就不一样了:在tap的回调里修改对象属性,会直接改变原对象的状态,因为流里传递的是对象的引用,所有订阅者拿到的都是同一个对象。
二、为什么经过tap后的Observable和源Observable不相等?
你第二次测试的代码:
const inputObj = { a: 'test obj prop', b: 5, c: ['x', 'y', 'z'] }; of(inputObj) === of(inputObj) .pipe( tap((input) => { input.a = 'changed prop value'; input.b = 60; input.c = ['1', '2', '3']; //return input; }) )? console.log('after tap:still equal'):console.log('after tap: Not equal');
输出是after tap: Not equal,这完全符合RxJS的设计,并没有违反“tap返回的Observable是源的精确镜像”的描述——这里的“镜像”指的是流中发出的值是源Observable值的精确引用(或值本身),流的事件序列(next、error、complete)完全一致,而不是说pipe(tap(...))返回的Observable实例和源Observable实例是同一个对象。
of(inputObj)是一个Observable实例,调用.pipe(tap(...))会返回一个新的Observable实例,所以用===比较两个不同的实例自然会返回false。但如果你订阅这两个Observable,会发现它们发出的是同一个对象引用——修改tap回调里的对象属性后,源Observable发出的对象也会被改变:
const source$ = of(inputObj); const tapped$ = source$.pipe(tap(input => input.a = 'changed')); source$.subscribe(val => console.log(val.a)); // 输出 'changed' tapped$.subscribe(val => console.log(val.a)); // 输出 'changed'
这才是“精确镜像”的真正含义:流的内容(发出的值、事件)完全一致,而不是Observable实例本身相等。
内容的提问来源于stack exchange,提问作者user1169587

