3

我正在使用 Angular2-RC.1,当我设置一个包含大数据的组件时,我发现性能很差。我有一个表格组件(包装 Handsontable),并公开了一个名为“数据”的可绑定输入属性。这个属性通常绑定到一个大数组(大约十万行)。

当我设置我的大型数据集时,更改检测会导致对主机组件中的整个数组(不是输入属性的所有者)进行值等价测试。

@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(); }
}

在这里,我展示了一个调用堆栈,显示更改检测是在整个数据上启动的。(粗体方法是我的主机组件的运行时自动生成的更改检测功能)而不是简单地比较引用。

调用栈

这是有意的行为吗?

4

2 回答 2

4

我自己找到了答案。独立的变更检测过程是比较参考(这是它的设计行为)。

但是,当未启用生产模式时,其他断言会对组件的数据执行等效测试。

于 2016-05-20T20:24:17.057 回答
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 中更改检测实际上会遍历所有(例如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() 方法应该在调用堆栈上。

于 2016-05-20T22:49:30.317 回答