JavaScript — асинхронный язык программирования. Из-за этого функции часто вызываются как колбеки других функций. Особенно много этого в браузере, где колбек на колбеке и колбеком погоняет. Пока мы работали с простыми функциями, это не вызывало никаких затруднений, но все меняется при использовании методов.
На этом этапе мы еще не работали с асинхронностью, но это не должно помешать понять идею. В двух словах: функция setTimeout принимает на вход функцию и время, после которого ее надо вызвать. Когда приходит время, она это делает. В общем-то и всё.
Попробуйте запустить такой код:
const printer = {
name: 'Hexlet',
print(greeting = 'hello') {
console.log(`${greeting}, ${this.name}`);
}
};
// Прямой запуск
printer.print(); // => "hello, Hexlet"
Теперь тот же самый код мы хотим вызвать не сразу, а через секунду. Для этого используем функцию setTimeout()
, которая вызывает переданную функцию через указанное количество времени.
// Хотим запустить метод print через секунду
// Обязательно запустите этот код на своем компьютере
// чтобы почувствовать то как работает setTimeout
// 1000 - означает 1000 миллисекунд или 1 секунда
// printer.print - это не вызов, а передача функции
setTimeout(printer.print, 1000);
// Спустя секунду
// => "hello, undefined"
Этот код выдаст 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(); // => "hello, Hexlet"
Это распространенное решение, которое заодно помогает захватить внешние переменные, когда они нужны для вызова:
// Оборачивание в функцию помогает передать какие-то данные внутрь
const value = 'hi';
setTimeout(() => printer.print(value), 1000);
// => "hi, Hexlet"
Связывание (Bind)
Другой способ — использование метода bind()
(переводится как связать). Метод bind()
доступен у функций, и в его задачу входит связывание функции с каким-то контекстом. Результатом выполнения bind()
будет новая функция, работающая как и исходная функция, но с привязанным к ней контекстом.
// Контекстом является тот же объект printer, в котором определен метод
// Это довольно странно выглядит, но жизнь — сложная штука
// bind вызывается на функции и возвращает функцию
const boundPrint = printer.print.bind(printer);
// Теперь можно так
boundPrint(); // => "hello, Hexlet"
setTimeout(boundPrint, 1000);
// Через секунду
// => "hello, Hexlet"
// Можно вызывать 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()
полезен там, где привязка контекста и вызов функции происходят в разных местах и, как правило, в разное время. Мы встретимся с таким кодом, когда перейдем к асинхронности в JavaScript.
Иногда вызов функций, использующих внутри себя this
, происходит сразу вместе с привязкой контекста. Это можно сделать напрямую, сразу же вызвав функцию, возвращаемую bind
: ...bind(/* контекст */)()
:
const print = printer.print;
print.bind(printer)('hi'); // => "hi, Hexlet"
а можно использовать специально созданные для этого функции apply()
и call()
:
// func.apply(thisArg, [ argsArray])
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
const numbers = [1, 10, 33, 9, 15];
const max = Math.max.call(null, ...numbers); // 33
Вызов выше просто демонстрация, с практической точки зрения он мало полезен. Реальное использование call()
и apply()
проявляется в связке контекста с функциями из прототипов. Об этом поговорим в следующих уроках.
Дополнительные материалы

Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.