Angular中的纯粹和不纯的管道之间的本质区别以及为什么这很重要

在Angular中编写自定义管道时,可以指定是定义纯管道还是不纯管道:

@Pipe({
  name: 'myCustomPipe', 
  pure: false/true        <----- here (default is `true`)
})
export class MyCustomPipe {}

Angular在管道上有一个非常好的文档,你可以在这里找到。 但是,正如文件经常发生的情况一样,没有明确的划分理由。 在这篇文章中,我想填补这个空白,并展示了函数式编程的前景,它展示了纯和不纯的管道来自何处。 除了学习差异,你会知道它是如何影响性能,这些知识将帮助你编写高效和高性能的管道。

A pure function

网上有很多关于函数式编程的信息,可能每个开发人员都知道纯函数是什么。 对于我自己,我将一个纯函数定义为一个没有内部状态的函数。 这意味着它执行的所有操作都不受状态的影响,给出相同的输入参数产生相同的确定性输出。

以下是添加数字的两个版本的函数。 第一个是纯的,第二个是不纯的:

const addPure = (v1, v2) => {
  return v1 + v2;
};

const addImpure = (() => {
  let state = 0;
  return (v) => {
    return state += v;
  }
})();

如果我用相同的输入调用两个函数,比如说数字1,则第一个函数在每次调用时都会产生相同的输出2:

addPure(1, 1);  // 2
addPure(1, 1);  // 2
addPure(1, 1);  // 2

而第二个产生不同的输出:

addImpure(1);  // 1
addImpure(1);  // 2
addImpure(1);  // 3

所以这里的关键是即使输入不变,不纯的函数也会产生不同的输出。 这意味着我们不能使用输入值来确定输出是否会改变。

纯函数:

  • 输入参数值决定了输出,所以如果输入参数不改变,输出不会改变
  • 可以多次调用,而不会影响输出结果

非纯函数:

  • 不能使用输入值来确定输出是否会改变
  • 不能共用,因为内部状态会受到外界的影响

Applying that knowledge to Angular pipes

假设我们定义了一个自定义管道,纯管道:

@Pipe({
  name: 'myCustomPipe', 
  pure: true
})
export class MyCustomPipe {}

在组件模板中像这样使用它:

<span>{{v1 | customPipe}}</span>
<span>{{v2 | customPipe}}</span>

因为管道是纯的,这意味着没有内部状态,管道可以被共享。Angular是怎样实现的?尽管模板中有,但它可以只创建一个管道实例,该实例可以在用法之间共享。
对于那些从我以前的文章中了解到组件工厂的人来说,这里有一个相关的编译代码,它只定义了一个管道定义:

function View_AppComponent_0(_l) {
  return viewDef_1(0, [
    pipeDef_2(0, ExponentialStrengthPipe_3, []), // node index 0
    ...

这是在updateRenderer函数中共享的:

function(_ck,_v) {
    unwrapValue_7(_v,4,0,_ck(_v,5,0,nodeValue_8(_v, 0),...);
                                                   ^^^
    unwrapValue_7(_v,8,0,_ck(_v,9,0,nodeValue_8(_v, 0),...);
                                                   ^^^

这里是unwrapValue函数用于通过调用transform来检索当前的管道值。 管道实例由nodeValue函数调用中的节点索引引用 - 在这种情况下为0。

但是,如果我们将管道定义为不纯,假设有一些内部状态:

@Pipe({
  name: 'myCustomPipe', 
  pure: false
})
export class MyCustomPipe {}

我们不希望第二次使用中的管道在第一次使用时受到其调用的影响,所以Angular会创建两个管道实例,每个实例都有自己的状态:

function View_AppComponent_0(_l) {
   return viewDef(0, [
       ...
       pipeDef_2(0, ExponentialStrengthPipe, []) // node index 4
       ...
       pipeDef_2(0, ExponentialStrengthPipe, []) // node index 8

并且它在updateRenderer函数中不共享:

function(_ck,_v) {
    unwrapValue_7(_v,4,0,_ck(_v,5,0,nodeValue_8(_v, 4),...);
                                                   ^^^
    unwrapValue_7(_v,8,0,_ck(_v,9,0,nodeValue_8(_v, 8),...);
                                                   ^^^

你可以在这里看到,对于每个用法Angular使用不同的节点索引 - 4和8来代替节点索引0。

第一章总结的第二点是,用纯函数我们可以用输入值来确定输出是否会变化,而不纯的函数则不能保证。

在Angular中,我们将输入参数传递给管道,如下所示:

<span>{{v1 | customPipe:param1:param2}}</span>

所以如果一个管道是纯的,我们知道它的输出(通过变换方法)是由输入参数严格确定的。 如果输入参数不改变,输出不会改变。 这个推理允许Angular只有在输入参数改变时才优化管道和调用变换方法。

但是,如果管道不纯并且具有内部状态,则相同的参数不能保证相同的输出。 这意味着Angular被迫在每个摘要上的管道实例上触发转换函数。

不纯管道的一个很好的例子是来自@ angular / common包的AsyncPipe。 此管道具有内部状态,该状态持有通过订阅传递给管道的observable作为参数创建的基础订阅。 由于Angular必须为每个管道使用创建一个新实例,以防止不同的可观察对象相互影响。 而且每个摘要上还必须调用变换方法,因为甚至认为可观察参数可能不会改变新的值,可能通过这个需要被变换检测处理的可观察到。

另外两个不纯的管子是JsonPipe和SlicePipe。 Angular对管道施加了额外的限制,被认为是纯粹的 - 管道的输入是不可变的。 如果输入是可变的,则需要在每个摘要上重新评估管道,因为可以在不更改对象引用(管道参数保持不变)的情况下改变输入对象。 这就是为什么尽管没有内部状态,JsonPipe和SlicePipe管道都不被认为是纯粹的。

其余的Angular默认管道是纯的。

Conclusion

所以,正如我们所看到的,如果不明智和谨慎使用不纯的管道,可能会有明显的性能下降。 性能受到影响的原因是Angular创建了不纯的管道的多个实例,并且在每个摘要循环中将其称为变换方法。

我希望通过阅读文章,您现在知道两种类型之间的区别,在设计和实现自定义管道时,Angular如何处理这两种类型,以及您应该使用哪种心智模型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值