Angular2使用值等价或引用相等来检测更改?

时间:2016-05-20 18:43:58

标签: typescript angular value-type reference-type angular2-changedetection

我正在使用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,显示在整个数据上启动了变化检测。 (粗体方法是我的主机组件的运行时自动生成的更改检测功能),而不是简单地比较引用。

callstack

这是故意的行为吗?

2 个答案:

答案 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()方法应位于调用堆栈上。