Метод .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 создаётся заново для каждого экземпляра — занимает чуть больше памяти при тысячах экземпляров.