Angular2 обнаруживает изменения, используя эквивалентность значений или равенство ссылок?
Я использую Angular2-RC.1 и я видел плохую производительность при настройке компонента, имеющего большие данные. У меня есть табличный компонент (wrapping 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(); }
}
Здесь я показываю callstack, показывающий, что обнаружение изменений запускается по всем данным. (метод bold - это функция обнаружения изменений, автоматически сгенерированная во время выполнения для компонента my host) вместо того, чтобы просто сравнивать ссылки.
Является ли это намеренным поведением?
2 ответа:
Я нашел ответ сам. Автономный процесс обнаружения изменений-это сравнение ссылок (это его поведение по дизайну).
Но если производственный режим не включен, то дополнительные утверждения выполняют проверку эквивалентности данных вашего компонента.
Хотя @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); } }
Итак, если привязка шаблона содержит что-то, что является iterable - напр.,
[arrayInputProperty]="parentArray"
– тогда в devMode обнаружение изменений фактически повторяется через все (напр.parentArray
) элементы и сравнивает их, даже если нет цикла NgFor или чего-то еще, что создает привязку шаблона к каждому элементу. Это очень отличается от проверкиlooseIdentical()
, которая выполняется в prodMode. Для очень больших итераций это может оказать значительное влияние на производительность, как в сценарии OP.
areIterablesEqual()
находится в коллекции .ТС и это просто перебирает итерационные таблицы и сравнивает каждый элемент. (Поскольку ничего интересного не происходит, я не включил код здесь.)От Ланга.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()
. Когда срабатывает точка останова, вид *.методы detectChangesInternal () должны находиться в стеке вызовов.