angular如何解决无限递归?

How does angular resolve infinite recursion?

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

我正在学习关于字符串插值的第 2.13 部分。

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

的属性之一
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 使用什么逻辑来决定 'real time' 模板指令在重复 8 次后从 class 属性 中切断。

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#L383Angular 运行变化检测周期两次

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

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

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

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

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

{{ 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