Метод .bind() есть у любой функции — он унаследован от Function.prototype. Вызов возвращает новую функцию, идентичную оригиналу по поведению, но с зафиксированным this:

this связанной функции — тот объект, который был передан первым аргументом при .bind().

function describe(role) {
  return `${role}: ${this.name}, age ${this.age}`;
}

const user = { name: 'Аня', age: 28 };
const describeUser = describe.bind(user);

describeUser('admin');           // 'admin: Аня, age 28'
describeUser('editor');          // 'editor: Аня, age 28'

Оригинальная функция describe не изменилась — можно продолжать вызывать её с другим контекстом или другими аргументами. Но describeUser навсегда связана с user.

Разница с .call() / .apply()

Основное отличие — момент вызова:

function greet() { return `Hello, ${this.name}`; }
const person = { name: 'Аня' };

// .call() — вызвать прямо сейчас
greet.call(person);              // 'Hello, Аня'

// .bind() — создать новую функцию, вызвать позже
const boundGreet = greet.bind(person);
setTimeout(boundGreet, 1000);    // через секунду выведет 'Hello, Аня'

// эквивалент через arrow-обёртку
setTimeout(() => greet.call(person), 1000);

Практически: если нужен разовый вызов с явным контекстом — .call(). Если функцию нужно передать куда-то (callback, listener) и она должна помнить свой контекст — .bind().

Use case: event handlers в классах

Классический пример — передача метода класса как обработчика события:

class Dropdown {
  constructor(element) {
    this.element = element;
    this.isOpen = false;

    // без bind — this внутри toggle будет window/undefined
    // element.addEventListener('click', this.toggle);

    // с bind — this = экземпляр Dropdown
    element.addEventListener('click', this.toggle.bind(this));
  }

  toggle() {
    this.isOpen = !this.isOpen;
    this.element.classList.toggle('open', this.isOpen);
  }
}

Без .bind() метод toggle в момент клика вызывался бы браузером с this = element (стандартное поведение event listener), а не экземпляром Dropdown. С .bind(this) в конструкторе контекст зафиксирован на нужном объекте.

Есть ещё один момент: если понадобится removeEventListener, нужно передать ту же самую bound-функцию. Каждый вызов .bind() создаёт новую функцию — поэтому bound-версия сохраняется в свойство:

class Dropdown {
  constructor(element) {
    this.element = element;
    // сохраняем связанную функцию как свойство экземпляра
    this._boundToggle = this.toggle.bind(this);
    element.addEventListener('click', this._boundToggle);
  }

  destroy() {
    this.element.removeEventListener('click', this._boundToggle);
  }

  toggle() { /* ... */ }
}

Partial application: связывание аргументов

Помимо this, .bind() может зафиксировать и часть аргументов — это называется частичным применением (partial application):

function add(a, b, c) {
  return a + b + c;
}

// bind фиксирует this (null здесь неважно) и первые два аргумента
const add5and10 = add.bind(null, 5, 10);

add5and10(3);     // 18 (= 5 + 10 + 3)
add5and10(100);   // 115

Если контекст не важен, передают null первым аргументом. Дальше идут аргументы, которые нужно зафиксировать. Возвращается новая функция, ждущая оставшиеся аргументы.

В функциональном программировании этот паттерн активно используется. В JavaScript-коде встречается реже — чаще пишут стрелочную функцию-обёртку:

const add5and10 = (c) => add(5, 10, c);

Оба варианта работают одинаково. Bind-вариант короче, стрелочная — читабельнее для JavaScript-разработчиков без FP-фона.

Class fields как альтернатива

В классах ES2022 появились class fields, которые часто заменяют .bind() в конструкторе:

// раньше — bind в конструкторе
class Dropdown {
  constructor(element) {
    this.element = element;
    this.toggle = this.toggle.bind(this);
    element.addEventListener('click', this.toggle);
  }
  toggle() { /* ... */ }
}

// сейчас — class field с arrow-функцией
class Dropdown {
  constructor(element) {
    this.element = element;
    element.addEventListener('click', this.toggle);
  }
  toggle = () => { /* ... */ };   // this зафиксирован лексически
}

Class-field-arrow работает почти так же, как .bind(), но код чище и метод сразу «встроен» в экземпляр. Разница: bind-метод остаётся на прототипе (общий для всех экземпляров), а class-field-arrow создаётся заново для каждого экземпляра — занимает чуть больше памяти при тысячах экземпляров.