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() 方法应该在调用堆栈上。
我正在使用 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() 方法应该在调用堆栈上。