bind
Function.prototype.bind— метод, создающий новую функцию с фиксированным значениемthisи, опционально, предустановленными аргументами (частичное применение). Оригинальная функция не изменяется.
Зачем нужно
bind решает проблему потери контекста при передаче метода как callback. Также позволяет создавать функции с предустановленными аргументами (partial application), что упрощает код и делает его более декларативным.
Где используется
Обработчики событий в классах, React class components, передача методов в setTimeout/setInterval, частичное применение, создание специализированных функций из общих.
Предпосылки
this, call и apply, Замыкания (Closures)
Синтаксис
const boundFn = fn.bind(thisArg, arg1, arg2, ...);
thisArg— значениеthisдля новой функцииarg1, arg2, ...— аргументы, фиксированные в начале списка параметров- Возвращает новую функцию (оригинал не меняется)
Фиксация контекста
const user = {
name: 'Иван',
greet {
console.log(`Привет, ${this.name}`);
}
};
// Без bind — this потерян
const fn = user.greet;
fn; // "Привет, undefined"
// С bind — this зафиксирован
const boundGreet = user.greet.bind(user);
boundGreet; // "Привет, Иван"
// Работает в любом контексте
setTimeout(boundGreet, 100); // "Привет, Иван"
[1].forEach(boundGreet); // "Привет, Иван"
document.body.addEventListener('click', boundGreet); // "Привет, Иван"
Частичное применение (Partial Application)
function multiply(a, b) {
return a * b;
}
// Создаём специализированные функции
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(double(10)); // 20
Практический пример — логгер
function log(level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
}
// Фиксируем уровень
const logError = log.bind(null, 'ERROR');
const logInfo = log.bind(null, 'INFO');
const logDebug = log.bind(null, 'DEBUG');
logError(Date.now(), 'Что-то пошло не так');
// [ERROR] 1712345678: Что-то пошло не так
logInfo(Date.now(), 'Приложение запущено');
// [INFO] 1712345678: Приложение запущено
Фиксация нескольких аргументов
function request(method, baseUrl, path, body) {
console.log(`${method} ${baseUrl}${path}`, body || '');
}
const apiRequest = request.bind(null, 'GET', 'https://api.example.com');
apiRequest('/users'); // GET https://api.example.com/users
apiRequest('/users/1'); // GET https://api.example.com/users/1
const postApi = request.bind(null, 'POST', 'https://api.example.com');
postApi('/users', { name: 'Иван' });
// POST https://api.example.com/users { name: 'Иван' }
bind в классах
class Timer {
constructor {
this.seconds = 0;
// Способ 1: bind в конструкторе
this.tick = this.tick.bind(this);
}
tick {
this.seconds++;
console.log(`Секунд: ${this.seconds}`);
}
// Способ 2: class field + arrow (современный)
tickArrow = () => {
this.seconds++;
console.log(`Секунд: ${this.seconds}`);
};
start {
setInterval(this.tick, 1000); // this не потеряется
}
}
React class component
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = { query: '' };
// Привязка методов
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ query: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
this.props.onSearch(this.state.query);
}
render {
return (
<form onSubmit={this.handleSubmit}>
<input value={this.state.query} onChange={this.handleChange} />
</form>
);
}
}
Особенности bind
Повторный bind не работает
function greet() {
console.log(this.name);
}
const obj1 = { name: 'Первый' };
const obj2 = { name: 'Второй' };
const bound1 = greet.bind(obj1);
const bound2 = bound1.bind(obj2); // Повторный bind!
bound2; // "Первый" — первый bind побеждает
bind и new
function User(name) {
this.name = name;
}
const BoundUser = User.bind({ name: 'Игнорируется' });
const user = new BoundUser('Иван');
console.log(user.name); // "Иван" — new игнорирует привязанный this
Свойство name
function original() {}
const bound = original.bind(null);
console.log(bound.name); // "bound original"
Нет prototype
function fn() {}
const bound = fn.bind(null);
console.log(fn.prototype); // {}
console.log(bound.prototype); // undefined
Простая реализация (polyfill)
// Упрощённый polyfill для понимания механизма
Function.prototype.myBind = function(context, ...boundArgs) {
const fn = this;
return function(...callArgs) {
return fn.apply(context, [...boundArgs, ...callArgs]);
};
};
function greet(greeting, name) {
return `${greeting}, ${this.title} ${name}`;
}
const obj = { title: 'Господин' };
const bound = greet.myBind(obj, 'Здравствуйте');
console.log(bound('Иванов')); // "Здравствуйте, Господин Иванов"
Полная реализация (с поддержкой new)
Function.prototype.myBind = function(context, ...boundArgs) {
const fn = this;
const bound = function(...callArgs) {
// Если вызвано через new — используем новый объект как this
const isNew = this instanceof bound;
return fn.apply(
isNew ? this : context,
[...boundArgs, ...callArgs]
);
};
// Наследуем прототип оригинала
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
return bound;
};
bind vs стрелочная функция
class Component {
constructor {
this.value = 42;
}
// Вариант 1: bind
method1 { return this.value; }
// Нужно: this.method1 = this.method1.bind(this) в конструкторе
// Вариант 2: class field arrow
method2 = () => this.value;
// Автоматическая привязка, НО: создаётся для каждого экземпляра
}
| Аспект | bind | Arrow field |
|---|---|---|
| Создание | Одна функция в прототипе | Копия в каждом экземпляре |
| Память | Эффективнее | Расход на каждый экземпляр |
| Переопределение в наследнике | Возможно | Сложнее |
| Синтаксис | Более громоздкий | Компактный |
Частые ошибки
1. Забытый bind в обработчике
class App {
constructor {
this.data = [1, 2, 3];
}
handleClick {
console.log(this.data); // undefined без bind!
}
}
const app = new App();
// btn.onclick = app.handleClick; // Плохо
// btn.onclick = app.handleClick.bind(app); // Хорошо
2. bind в render (React) — создание новой функции при каждом рендере
// Плохо — новая функция на каждый render
render {
return <button onClick={this.handleClick.bind(this)} />;
}
// Хорошо — bind в конструкторе (один раз)
constructor {
this.handleClick = this.handleClick.bind(this);
}
3. Ожидание изменения контекста после bind
const bound = fn.bind(obj1);
// bound уже зафиксирован — call/apply не изменят this
bound.call(obj2); // this всё равно obj1
Практика
- Создай метод объекта и передай его в setTimeout — исправь потерю this через bind
- Реализуй частичное применение: из
add(a, b)создайadd5(b)через bind - Напиши свой polyfill
myBindбез поддержки new - Создай класс с несколькими методами и привяжи их в конструкторе
- Сравни bind и стрелочную функцию для привязки this в обработчике
Связанные темы
Ресурсы
- MDN — Function.prototype.bind
- JavaScript.info — Привязка контекста
- JavaScript.info — Частичное применение
🎓 Источник: Частичное применение и каррирование
- 📅 2018-10-08 · YouTube · ID:
ND8KQ5xjk7o - Тезисы:
bindзакрепляет аргументы слева направо- Первый аргумент
bind—this. Если не нужен —null - Лямбды (arrow) не привязываются через
bindк контексту —thisостаётся лексическим bind— встроенная реализация частичного применения- Каррирование через
bind:curry = fn => (...a) => a.length >= fn.length ? fn(...a) : curry(fn.bind(null, ...a))
- Цитата: «Первый аргумент в баинде необходим для того, чтобы привязывать функцию к объектному контексту. Но мы объектные контексты не используем. Поэтому мы туда передаем null.»
⚡ Источник: Как работает this · AsForJS
- 📅 2023-05-07 · YouTube · ID:
4tg4qokVS9o - Тезисы:
bind— высший приоритет дляthis, перекрывает даже поведение API- В
addEventListenerобычноthis = currentTarget, ноbind(ctx)фиксируетctx - На стрелочных функциях
bindдляthisНЕ работает (для аргументов работает)