Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Связывание (bind) JS: Введение в ООП

JavaScript — асинхронный язык программирования. Из-за этого функции часто вызываются как колбеки других функций. Особенно много этого в браузере, где колбек на колбеке и колбеком погоняет. Пока мы работали с простыми функциями, это не вызывало никаких затруднений, но все меняется при использовании методов.

На этом этапе мы еще не работали с асинхронностью, но это не должно помешать понять идею. В двух словах: функция setTimeout принимает на вход функцию и время, после которого ее надо вызвать. Когда приходит время, она это делает. В общем-то и всё.

Попробуйте запустить такой код:

const printer = {
  name: 'Hexlet',
  print(greeting = 'hello') {
    console.log(`${greeting}, ${this.name}`);
  }
};

// Прямой запуск
printer.print();

// Хотим запустить метод print через секунду
// Обязательно запустите этот код на своем компьютере
// чтобы почувствовать то как работает setTimeout
setTimeout(printer.print, 1000);

Этот код выдаст hello, undefined. Почему? Потому что внутрь setTimeout() мы передали не объект printer, а функцию print() без объекта. А значит эта функция потеряла связь с самим объектом и ее this больше не указывает на объект. Вот так можно проиллюстрировать то, что происходит:

const print = printer.print;
// Где-то внутри setTimeout
print(); // hello, undefined

Если контекста нет, то this оказывается равным пустому объекту, если мы говорим про обычные функции.

Такое поведение часто нежелательно. Практически всегда, когда передается метод, подразумевается, что он будет вызван в контексте того объекта, которому он принадлежит. Существует несколько способов добиться такого поведения. Самый простой — обернуть функцию в функцию, пока мы вызываем функцию.

setTimeout(() => printer.print(), 1000);
// hello, Hexlet

// Или без setTimeout
const fn = () => printer.print();
// Все работает потому что print() вызывается из printer
fn();

Это распространенное решение, которое заодно помогает захватить внешние переменные, когда они нужны для вызова:

const value = 'hi';
setTimeout(() => printer.print(value), 1000);
// hi, Hexlet

Другой способ — использование метода bind() (переводится как связать). Метод bind() доступен у функций, и в его задачу входит связывание функции с каким-то контекстом. Результатом выполнения bind() будет новая функция, работающая как и исходная функция, но с привязанным к ней контекстом.

// Контекстом является тот же объект printer, в котором определен метод
// Это довольно странно выглядит, но жизнь — сложная штука
// bind вызывается на функции и возвращает функцию
const bindedPrint = printer.print.bind(printer);

// Теперь можно так 
bindedPrint(); // hello, Hexlet
setTimeout(bindedPrint, 1000);

// Можно вызывать bind прямо по месту
setTimeout(printer.print.bind(printer), 1000);
// hello, Hexlet

Связанная функция сливается со своим контекстом "намертво". Больше this не поменяется.

Кроме контекста, bind() принимает на вход параметры, которые нужны функции. Причем не сразу все, а любую их часть. bind() подставит их в новую функцию (ту, что вернется из метода bind()) "частично". Эта техника называется "частичное применение функции". Так можно сразу применить нужные аргументы:

setTimeout(printer.print.bind(printer, 'hi'), 1000);
// hi, Hexlet

Подход с bind() был популярен до появления стрелочных функций, сейчас его используют нечасто, и по возможности стоит его избегать. Стрелочные функции проще для понимания и используются повсеместно.

Apply & Call

bind() полезен там, где привязка контекста и вызов функции происходят в разных местах и, как правило, в разное время. Однако нередко вызов происходит сразу вместе с привязкой. Это можно сделать напрямую, сразу же вызвав функцию, возвращаемую bind: ...bind(/* контекст */)(), а можно использовать специально созданные для этого функции apply() и call():

// func.apply(thisArg, [ argsArray])
const print = printer.print;
print.apply(printer, ['hi']); // hi, Hexlet

// func.call([thisArg[, arg1, arg2, ...argN]])
print.call(printer, 'hi'); // hi, Hexlet

Эти функции внутри себя делают две вещи: меняют контекст и сразу же производят вызов функции. Разница лишь в том, как они работают с аргументами этих функций: apply() – принимает аргументы в виде массива вторым параметром, а call() ждёт на вход позиционные аргументы.

Эти функции позволяют делать довольно необычные вещи, например так:

// Если контекста нет, то передают null
const numbers = [1, 10, 33, 9, 15]
const max = Math.max.apply(null, numbers); // 33

Вызов выше просто демонстрация, с практической точки зрения он мало полезен. Реальное использование call() и apply() проявляется в связке контекста с функциями из прототипов. Об этом поговорим в следующих уроках.


Дополнительные материалы

  1. Частичное применение

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
29 сентября 8 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
29 сентября 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»