Angular2 使用值等价或引用相等检测变化?

Angular2 detect changes using value equivalence or reference equality?

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

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

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

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

这是故意的行为吗?

我自己找到了答案。 独立的更改检测过程正在比较引用(这是它设计的行为)。

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

虽然@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 循环或其他创建模板绑定到每个元素的东西。这与在 prodMode 中执行的 looseIdentical() 检查非常不同。对于非常大的迭代,这可能会对性能产生重大影响,就像在 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() 方法应该在调用堆栈上。