内容来自于Max Koretskyi aka Wizard的《A gentle introduction into change detection in Angular》初次相遇让我们从一个简单的Angular组件开始。他表现应用程序的变化检测。这个时间戳的精度为毫秒。点击triggers按钮触发检测:@Component({ selector: ‘my-app’, template: <h3> Change detection is triggered at: <span [textContent]="time | date:'hh:mm:ss:SSS'"></span> </h3> <button (click)="0">Trigger Change Detection</button> })export class AppComponent { get time() { return Date.now(); }}如你所见,这是相当基本的。有一个名为time的getter返回当前时间戳。并且,我将它绑定到HTML中的span元素。当Angular运行变化检测时,它获取time属性的值,通过日期管道传递它,并使用结果更新DOM。这一切都很正常,但当我打开控制台的时候,我看到了一个错误:ExpressionChangedAfterItHasBeenCheckedError。事实上,这让我们感到非常惊讶。通常这个错误出现在更加复杂的程序上。但为什么一个如此简单的功能会导致这个错误呢?别担心,我们现在就来查看他的原因。让我们先从错误消息开始:Expression has changed after it was checked. Previous value: “textContent: 1542375826274”. Current value: “textContent: 1542375826275”.它告诉我们,textContent绑定的值是不同的。的确,毫秒不相同。因为Angular通过表达式time | date:‘hh:mm:ss:SSS’计算了两次,并比较了结果。它检测到了两次值的差异,这就是导致错误的原因。但Angular为什么要这样做?或者它什么时候做的?在我们了解这些问题的答案之前,我们还需要了解另外一些东西。组件视图和绑定Angular的变化检测主要有两个部分:组件视图相关绑定每一个Angular的组件都有一个HTML元素。当Angular创建DOM节点并将内容渲染到屏幕上,它需要一个地方来储存DOM节点的引用。为了实现这一目标,Angular内部有一个被称为View的数据结构。它还用于存储对组件实例的引用和绑定表达式之前的值。并且视图和组件之间的关系是一一对应的。下图展示了该关系:当编译器分析模板时,它会辨识在变化检测期间可能需要更新的DOM元素属性。每一个这样的属性,编译器都会创建一个绑定。绑定定义要更新的属性名和Angular用来获取新值的表达式。在我们的例子当中,time属性用于textContent的表达式中。所以,Angular会创建绑定来连接它和span元素。实际上,绑定不是包含所有必要信息的单个对象。viewDefinition定义模板元素和要更新的属性的实际绑定。用于绑定的表达式在updateRenderer方法中。*检查组件视图如你所知,Angular会对每一个组件执行变化检测。现在我们知道每个组件在Angular内部被称为视图(view),我们可以说Angular对每个视图执行了变化检测。当Angular检查视图时,它只需运行编译器为视图生成的所有绑定。它计算表达式并将它们的结果与视图上旧值数组中存储的值(oldValues)进行比较。这就是脏检查这个名字的由来。如果检测到差异,它会更新与绑定相关的DOM属性。它还需要将新值放入视图的旧值数组中。就这样。您现在有了更新的用户界面。一旦完成当前组件的检查,它将对子组件重复完全相同的步骤。在我们的应用程序中,在App组件中span元素的属性textContent只有一个绑定。所以在变化检测期间,Angular会读取组件time属性的值,再使用date管道,并将它与视图中存储的先前值进行比较。如果检测到不同,Angular会更新span旧值(oldValues)数组中的textContent属性.但是错误又从哪里出来的呢?在开发模式下,每个变化检测周期之后,Angular会同步运行另外一个检查,已确保表达式产生的值与之前变化检测运行期间的值相同。该检查不是原始检查的一部分,它在对整个组件树的检查完成后运行,并执行完全相同的步骤。然而,当这一次变化检测期间,如果检测到不同那个的值,Angular不会去更新DOM,相反的,它会直接抛出错误ExpressionChangedAfterItHasBeenCheckedError。但是Angular为什么要这样做?现在我们知道什么时候抛出错误了。但是为什么Angular需要这个检测。假设在变化检测运行期间,又有一些组件的一些属性被更新。此时,表达式产生的新值与用户界面中呈现的值不一样。这个时候Angular应该怎么做?它当然也可以另外再运行一个变化检测周期来使应用程序状态与用户界面同步。但如果在这期间,又有一些属性被更新了呢?看到问题了吗?实际上Angular可能会在变化检测的无限循环中结束。这种情况在AngularJS中经常发生。为了避免这种事情,Angular强制让数据单向流动。这种在变更检测和结果表达式变更后运行的检查是强制机制。一旦Angular处理了当前组件的绑定,就不能再更新绑定表达式中使用的组件属性。