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 3

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 () должны находиться в стеке вызовов.