英文:
Rxjs what is the meaning of "You can mutate objects as they pass through the tap operator's handlers." in Tap operator
问题
在链接点击中提到:
"小心!当对象通过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);}
);
}
控制台输出是:
Observable 3 starts
1
2
3
4
5
那么,tap返回的Observable与源Observable是完全一样的,那么"小心!当对象通过tap操作符的处理程序时,您可以突变对象。"这句话的意思是什么?
*************[2023年7月7日编辑] *******************************
根据Ruth的答案,我尝试比较tap之前的输入Observable和tap之后的输出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
所以这变成了另一个问题,它违反了关于tap返回的Observable是源的完全镜像的API。
英文:
in the link tap, it mentions
"Be careful! You can mutate objects as they pass through the tap operator's handlers."
What is the meaning of this?
I try the following test
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);}
);
}
the console output is
Observable 3 starts
1
2
3
4
5
so The observable returned by tap is an exact mirror of the source, but then what is the meaning of the statement
> Be careful! You can mutate objects as they pass through the tap operator's handlers.
?
*************[Edit on 20230707] *******************************
respond with Ruth answer, I try to compare the input observable before tap and output observable after tap by ===, and they are not equals.
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');
the output is
after tap: Not equal
so this becomes another problem, it violates the API about
> The observable returned by tap is an exact mirror of the source
答案1
得分: 2
除了@Picci的出色解释外,我想补充一下为什么你的输入没有改变。
这是因为你传递的是数字,而不是在文档中提到的_对象_给tap
。数字是原始类型,根据定义,原始类型是不可变的。
然而,当你传递一个对象时,你可以看到它在tap
内部如何被改变:
const { of } = rxjs;
const { tap } = rxjs.operators;
const inputObj = {
a: 'test obj prop',
b: 5,
c: ['x', 'y', 'z']
};
console.log('Before `tap`:', inputObj);
of(inputObj)
.pipe(
tap((input) => {
input.a = 'changed prop value';
input.b = 'number -> string change';
input.c = [1, 2, 3];
return input;
})
)
.subscribe(() =>
console.log('After `tap`:', inputObj)
);
英文:
In addition to the excellent explanation by @Picci, I'd like to add why your input didn't change.
It's because you passed numbers, not objects, as mentioned in the docs, to the tap
. Numbers are primitives, and by definition primitives are immutable.
However, when you pass an object, you could see how it could be mutated inside the tap
:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const { of } = rxjs;
const { tap } = rxjs.operators;
const inputObj = {
a: 'test obj prop',
b: 5,
c: ['x', 'y', 'z']
};
console.log('Before `tap`:', inputObj);
of(inputObj)
.pipe(
tap((input) => {
input.a = 'changed prop value';
input.b = 'number -> string change';
input.c = [1, 2, 3];
return input;
})
)
.subscribe(() =>
console.log('After `tap`:', inputObj)
);
<!-- language: lang-css -->
.as-console-wrapper { max-height: 100% !important; top: 0; }
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js"></script>
<!-- end snippet -->
答案2
得分: 1
rxjs遵循一种函数式方法。这意味着它鼓励使用“纯函数”。 “纯函数”应遵循不可变性概念,即应接收输入,处理该输入而不更改它,并返回输出。它们不应具有“副作用”,即不应更改函数范围之外的任何内容。这些概念应提高代码的可读性、可理解性和可测试性。
同时,没有副作用的程序基本上是无用的。因此,通常在纯函数处理的边缘有一个地方,副作用会发生。
“tap”操作符是用于在rxjs中实现“副作用”的操作符。在实际情况下,tap
接收一个输入值,执行操作,然后返回接收到的相同输入值。如果接收到的输入值是一个对象,那么该对象可以发生变化。因此,你在文档中读到的警告。
在你的情况下,你没有看到任何变化,因为你所做的不是“更改输入”(它是一个数字,因此不能被更改)。你输入的数字与tap
返回的相同。
还有一个概念:观察者。
tap
期望的输入是一个Observer
。一个Observer
是一个类型为
interface Observer<T> {
next: (value: T) => void
error: (err: any) => void
complete: () => void
}
的对象,这正是subscribe
函数期望的相同输入。因此,你可以想象tap
是放置在pipe
操作符链中间的一种订阅。
你使用tap
和subscribe
的方式是一种简化的方式,只传递一个函数给它们,这意味着你只传递了next
函数,忽略了error
和complete
。
英文:
rxjs follows a functional approach. Which means that it encourages the use of "pure functions". "pure functions" should follow the immutability concept, i.e. should receive input, elaborate that input without changing it and return the output. They should not have "side effects", i.e. they should not change anything outside the scope of the function. These concepts should enhance readability, understandability and testability of sw.
At the same time, programs without side effects are basically useless. So there must be a place, usually at the margin of pure function processing, where side effects take place.
The tap
operator is the operator designed to implement "side effects" in rxjs. In practical terms tap
receives an input value, does whatever, and then returns that same input value received. If the input value received is an object, that object can be mutated. Hence the warning you read in the documentation.
In you case you do not see any mutation since what you do is not "change the input" (which is a number and therefore can not be changed). The number you get in input is the same one tap
returns.
One more concept: Observers.
What tap
expects in input is an Observer
. An Observer
is an object of type
interface Observer<T> {
next: (value: T) => void
error: (err: any) => void
complete: () => void
}
which is exactly the same input expected by the function subscribe
. Hence you can imagine that tap
is a sort of subscription placed in the middle of a pipe
of operators.
The way you have used tap
and subscribe
passing to them just one function is a simplified way of using tap
and subscribe
and means that you are just passing the next
function, ignoring error
and complete
.
答案3
得分: 0
这与接收对象作为参数的任何其他函数一样,都会收到相同的警告,因为这些参数是作为引用传递而不是作为值传递的。
我们可以设计我们自己的 tap
函数,它会受到相同的影响:
const tap = fn => x => (fn(x), x);
const obj = {foo: 'bar'};
( tap(x => console.log(x.foo = 42)) //<- mutate
)(obj);
console.log(obj); //<- mutated! not {foo:'bar'} anymore
英文:
This is the same warning as for any other functions receiving objects as arguments as those are passed as references and not as values.
We can design our own tap
function which would be affected in the same way:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const tap = fn => x => (fn(x), x);
const obj = {foo: 'bar'};
( tap(x => console.log(x.foo = 42)) //<- mutate
)(obj);
console.log(obj); //<- mutated! not {foo:'bar'} anymore
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论