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

Практика

  1. Создай метод объекта и передай его в setTimeout — исправь потерю this через bind
  2. Реализуй частичное применение: из add(a, b) создай add5(b) через bind
  3. Напиши свой polyfill myBind без поддержки new
  4. Создай класс с несколькими методами и привяжи их в конструкторе
  5. Сравни bind и стрелочную функцию для привязки this в обработчике

Связанные темы

Ресурсы


🎓 Источник: Частичное применение и каррирование

  • 📅 2018-10-08 · YouTube · ID: ND8KQ5xjk7o
  • Тезисы:
    • bind закрепляет аргументы слева направо
    • Первый аргумент bindthis. Если не нужен — 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 НЕ работает (для аргументов работает)