虽然@Jairo 已经回答了这个问题,但我想更详细地记录他在评论中提到的代码流(所以我不必再次挖掘源代码来找到这个):
在更改检测期间,来自view_utils.ts的代码执行:
export function checkBinding(throwOnChange: boolean, oldValue: any, newValue: any): boolean {
if (throwOnChange) { // <<------- this is set to true in devMode
if (!devModeEqual(oldValue, newValue)) {
throw new ExpressionChangedAfterItHasBeenCheckedException(oldValue, newValue, null);
}
return false;
} else {
return !looseIdentical(oldValue, newValue); // <<--- so this runs in prodMode
}
}
从change_detection_util.ts 开始,这里是仅在 devMode 中运行的方法:
export function devModeEqual(a: any, b: any): boolean {
if (isListLikeIterable(a) && isListLikeIterable(b)) {
return areIterablesEqual(a, b, devModeEqual); // <<--- iterates over all items in a and b!
} else if (!isListLikeIterable(a) && !isPrimitive(a) && !isListLikeIterable(b) &&
!isPrimitive(b)) {
return true;
} else {
return looseIdentical(a, b);
}
}
因此,如果模板绑定包含可迭代的内容——例如[arrayInputProperty]="parentArray"
——然后在 devMode 中更改检测实际上会遍历所有(例如parentArray
)项目并比较它们,即使没有 NgFor 循环或其他创建模板绑定的东西到每个元素。looseIdentical()
这与在 prodMode 中执行的检查非常不同。对于非常大的可迭代对象,这可能会对性能产生重大影响,就像在 OP 场景中一样。
areIterablesEqual()
在collection.ts中,它只是遍历可迭代对象并比较每个项目。(由于没有什么有趣的事情发生,我没有在这里包含代码。)
来自lang.ts(这是我认为我们大多数人一直认为的变化检测,并且只是在 devMode 或 prodMode 中):
export function looseIdentical(a, b): boolean {
return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
}
感谢@Jairo 对此进行了深入研究。
自我注意:要轻松找到 Angular 为组件创建的自动生成的更改检测对象,请放入{{aMethod()}}
模板并在方法内设置断点aMethod()
。当断点触发时,View *.detectChangesInternal() 方法应该在调用堆栈上。