Function.prototype.bind - встроенная функция, которая создает новую функцию с контекстом, привязанным к контексту, заданному параметром. Основным вариантом использования этой функции в шаблоне Angular является привязка свойства (@Input) функции.

<app-test-component 
  [testFunction]="printValue.bind(this)"></app-test-component>

Это помогает поддерживать контекстную область, когда функция выполняется внутри компонента. В настоящее время это относительно редкая функция, и некоторые разработчики Angular, возможно, даже не видели ее раньше. Тем не менее, если вы использовали его в своих шаблонах, я настоятельно рекомендую вам прекратить это делать, поскольку это приводит к проблемам с производительностью.

Проблема

Короче говоря, основная проблема заключается в том, что вызовы функций в шаблоне не подходят для механизма обнаружения изменений Angular. Каждый раз, когда срабатывает обнаружение изменений, все вызовы функций в шаблоне всегда будут повторно выполняться, включая Function.prototype.bind. Для более подробного объяснения, пожалуйста, ознакомьтесь с другой статьей, которую я написал ранее:



Поскольку Function.prototype.bind является относительно медленной функцией, она может очень легко стать убийцей производительности. Вот простой пример, демонстрирующий проблему. Для начала мы модифицируем встроенную функцию, чтобы добавить журнал, чтобы мы знали, когда он вызывается:

let calledTimes = 0;
const originalBind = Function.prototype.bind;
Object.defineProperty(
  Function.prototype,
  'bind',
  {
    value: function bind(context) {
      console.log(`bind called ${++calledTimes} times`);
      return originalBind.apply(this, arguments);
    }
  }
);

Затем мы создаем простой компонент, который может принимать свойство Function:

@Component({
  selector: 'app-test-component',
  template: `
    <button (click)="testFunction()">Call function</button>
  `
})
export class TestComponent {
  @Input()
  testFunction = () => {};
}

Наконец, мы добавляем этот компонент в наш корень:

@Component({
  selector: 'app-root',
  template: `
    <button (click)="triggerChangeDetection()">
      Trigger change detection
    </button>
    <app-test-component 
      [testFunction]="printValue.bind(this)"></app-test-component>
  `
})
export class AppComponent {
  value = 1;

  triggerChangeDetection() {
    console.log("Change detection triggered");
  }

  printValue() {
    console.log(this.value);
  }
}

Теперь мы можем попытаться активировать обнаружение изменений, нажав кнопку.

Каждый раз, когда нажимается кнопка (что вызывает обнаружение изменений), функция Function.prototype.bind всегда будет повторно выполняться.

(Ожидается, что функция будет вызываться дважды за цикл обнаружения в режиме разработки. Это функция отладки Angular. Вот более подробное объяснение.)

Это становится катастрофой, когда есть функция, которая часто запускает обнаружение изменений, например, прослушивание mousemove событий.

@Component({
  template: `
    <button (mousemove)="triggerChangeDetection()">
      Trigger change detection
    </button>
    ...
  `,
  ...
})
export class AppComponent {...}

Решение

Способ решить эту проблему на самом деле проще, чем вы можете себе представить: просто не используйте Function.prototype.bind. Основная причина, по которой мы используем здесь привязку, - это поддержание this области контекста, но есть гораздо более чистая альтернатива. Что нам нужно сделать, так это включить обычную функцию:

printValue() {
  console.log(this.value);
}

в стрелочную функцию, не создающую дополнительной области контекста:

printValue = () => {
  console.log(this.value);
}

Теперь мы можем просто применить привязку свойств к функции стрелки, не беспокоясь о контексте:

@Component({
  template: `
    ...
    <app-test-component 
      [testFunction]="printValue"></app-test-component>
  `
})
export class AppComponent {...}

Спасибо за прочтение! Надеюсь, эта статья окажется для вас полезной. Будем очень признательны за любые комментарии. : D