我正在使用Angular2-RC.1,当我设置具有大数据的组件时,我发现性能很差。 我有一个表格组件(包装Handsontable),我公开了一个名为“data”的可绑定Input属性。 此属性通常绑定到一个大型数组(大约十万行)。
当我设置我的大型数据集时,更改检测会导致对主机组件中整个数组(而不是输入属性的所有者)进行值等效测试。
@Component({
selector: "ha-spreadsheet",
template: "<hot-table [data]="data"></hot-table>",
directives: [ HotTable ],
encapsulation: ViewEncapsulation.Emulated
})
export class Spreadsheet implements OnActivate {
data: { rows: Array<Array<number>> };
load(service) { this.data = service.getLargeDataSet(); }
}
这里我展示了一个callstack,显示在整个数据上启动了变化检测。 (粗体方法是我的主机组件的运行时自动生成的更改检测功能),而不是简单地比较引用。
这是故意的行为吗?
答案 0 :(得分:4)
我自己找到了答案。 独立的变更检测过程是比较参考(这是它的设计行为)。
但是当生产模式未启用时,其他断言会对组件的数据执行等效性测试。
答案 1 :(得分:3)
虽然@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中,更改检测实际上会遍历所有(例如{ {1}})项目并对它们进行比较,即使没有NgFor循环或其他创建模板绑定到每个元素的内容。这与prodMode中执行的parentArray
检查非常不同。对于非常大的迭代,这可能会对OP场景产生重大的性能影响。
looseIdentical()
位于collection.ts中,它只是迭代迭代并比较每个项目。 (由于没有任何有趣的内容,我在这里没有包含代码。)
来自lang.ts(这是我认为我们大多数人认为变更检测始终只有 - 在devMode或prodMode中):
areIterablesEqual()
感谢@Jairo深入研究。
自我注意:为了轻松找到Angular为组件创建的自动生成的更改检测对象,将export function looseIdentical(a, b): boolean {
return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
}
放在模板中并在{{aMethod()}}
方法中设置断点。断点触发时, View *。detectChangesInternal()方法应位于调用堆栈上。