角度如何解决无限递归?

Posted

技术标签:

【中文标题】角度如何解决无限递归?【英文标题】:How does angular resolve infinite recursion? 【发布时间】:2019-03-09 08:18:03 【问题描述】:

我正在学习这个 Udemy 课程: https://www.udemy.com/the-complete-guide-to-angular-2/

我在第 2.13 部分的字符串插值。

作为课程的一部分,您在组件中定义一个简单的函数来显示您的类的属性之一:

export class ServerComponent
    serverId = 10;
    serverStatus = 'offline';

    getServerStatus()
        return this.serverStatus;
    

然后在您的组件 html 中,将该函数绑定到模板:

<P>Server with ID  serverId  is  getServerStatus() </P>

我想测试的是,当您将该字符串插值标记绑定到自引用函数时会发生什么...例如:

getServerStatus()
    this.serverStatus = this.serverStatus + this.serverStatus;
    return this.serverStatus;

当我运行这段代码时,我注意到页面显示如下:

server with ID 10 is offlineofflineofflineofflineofflineofflineofflineoffline

serverStatus 变量恰好重复 8 次。

我想知道为什么这个数量恰好是 8 次重复? Angular 使用什么逻辑来决定“实时”模板指令在 8 次重复后从类属性中被切断。

【问题讨论】:

我不确定,但我希望它是由角度生命周期的各个阶段触发的方法的最小评估次数。 奇怪的是,例如,当我在模板中多次显示组件时,在第一个实例上显示 8x,但在第二、第三和第四个实例中,每个实例仅显示 4x . 【参考方案1】:

Angular 在应用程序开始时启动两个更改检测周期

也就是说,它调用了两次Application.tick()方法

1) 引导主组件后 (https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L539-L541)

private _loadComponent(componentRef: ComponentRef<any>): void 
  this.attachView(componentRef.hostView);
  this.tick();

2) 并且在第一个 VM 轮次上(当 zonejs 中没有微任务时)(https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L385-L386)

this._zone.onMicrotaskEmpty.subscribe(
  next: () =>  this._zone.run(() =>  this.tick(); ); );

考虑到这一点,让我们回到我们的Application.tick() 方法。它在视图树(组件视图或嵌入视图)上运行更改检测。

tick(): void 
   ...
    try 
      ...
      this._views.forEach((view) => view.detectChanges());
      if (this._enforceNoNewChanges) 
        this._views.forEach((view) => view.checkNoChanges());
      
     catch (e) 
      ...
     finally 
      ...
    

我们可以在这里注意到什么?

我们可以注意到处于开发模式(因为this._enforceNoNewChanges = isDevMode(); https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L383)Angular 运行了两次更改检测周期

这里还有一点是tick方法是在try catch块内执行的。

那么,到目前为止我们有什么?

2 сd cycles * 2 view.detectChanges() on the tree = 4

还在每个view.detectChanges() Angular 上检查模板绑定是否已更改。为此,Angular 执行模板中的每个表达式(因此,您的 getServerStatus() 方法将在每次树遍历时执行)。如果在第二个 cd withih tick 方法期间绑定发生了一些变化,那么 Angular 会抛出错误 Expression has changed after it was checked。你可以猜到它不会停止后续的 cd 循环谢谢try catch 块。

为简单起见,假设您有以下模板:

 getServerStatus() 

那么这里发生了什么?

Start app                                                              serverStatus 

 loadComponent => tick
                    |
                    |__ view.detectChanges()
                                   ||
                                   \/
                           call getServerStatus()                     'offlineoffline'

                    |__ view.checkNoChanges()
                                  ||
                                  \/
                           call getServerStatus()               'offlineofflineofflineoffline'

               'offlineoffline' !== 'offlineofflineofflineoffline'
                                  ||
                                  \/
    ExpressionChangedAfterItHasBeenCheckedError (template is not updated!!)

 onMicrotaskEmpty => tick
                    |
                    |__ view.detectChanges()
                                   ||
                                   \/
                           call getServerStatus()                     'offline'.repeat(8)

                    |__ view.checkNoChanges()
                                  ||
                                  \/
                           call getServerStatus()                     'offline'.repeat(16)

               'offline'.repeat(8) !== 'offline'.repeat(16)
                                  ||
                                  \/
     ExpressionChangedAfterItHasBeenCheckedError (template is not updated!!)

因此,您会得到准确的 8 次 serverStatus 重复

【讨论】:

很棒的反应...谢谢!

以上是关于角度如何解决无限递归?的主要内容,如果未能解决你的问题,请参考以下文章

递归无限循环?

PHP二叉树递归遍历无限循环问题

如果使用 object.value 而不是 object,则角度递归模板列表将变为无限递归

使用密封的特征/案例类播放JSON:无限递归

递归调用太深,可能导致栈溢出

如何解决无限像素溢出的底部?