call, apply и bind
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
У функции нет собственного this — контекст выбирается в момент вызова, по тому, что стоит слева от точки. call, apply и bind позволяют назначить this вручную, а не надеяться на удачное место вызова.
Метод не принадлежит объекту — он просто лежит в его свойстве. Стоит присвоить метод переменной или передать куда-то как аргумент, и точка слева пропадает вместе с контекстом: this внутри уже не указывает на прежний объект. Как это выглядит в живом коде и чем ещё лечится, разобрано в статье «Потеря контекста и как её чинить».
call вызывает функцию немедленно, а первым аргументом принимает то, чем станет this. Остальные аргументы едут следом, как при обычном вызове. Это и есть явная привязка: контекст задан в коде, а не выведен из формы вызова.
apply делает ровно то же, что call, и отличается единственным: аргументы функции передаются одним массивом. Никакой разницы в скорости, поведении this или возвращаемом значении между ними нет — выбирайте по тому, в каком виде у вас уже лежат аргументы.
Мнемоника кривая, но помогает: call — Comma, аргументы через запятую; apply — Array, аргументы массивом.
| Вызов | Аргументы функции | Когда выполняется |
|---|---|---|
| f.call(ctx, a, b) | перечислены через запятую | сразу |
| f.apply(ctx, [a, b]) | одним массивом | сразу |
| f.bind(ctx, a) | запоминаются, остальные дописываются позже | при вызове полученной копии |
Оператор ... раскладывает массив в список аргументов, поэтому современный код чаще пишет call со spread или обходится вовсе без них. apply вы всё ещё встретите в чужих проектах и в статьях — знать его нужно, тянуться к нему первым делом необязательно.
bind ничего не вызывает. Он возвращает новую функцию — обёртку, внутри которой this уже зафиксирован. Исходная функция при этом не меняется, а копия работает независимо от объекта, из которого её достали.
Именно поэтому bind — рабочая лошадка для колбэков: обработчиков событий, таймеров, любых мест, где функцию отдают наружу и вызовут без объекта. А вот со стрелочными функциями он бесполезен: они берут this из места объявления, и привязать им контекст нельзя ни одним из трёх методов. Почему так — в статье «Стрелочные функции и this».
Привязанная функция — это обёртка, которая при любом вызове подставляет запомненный контекст и игнорирует переданный. Повторный bind оборачивает обёртку, но внутренний слой всё равно ставит своё значение. Перепривязать нельзя — только собрать новую копию от исходной функции.
После контекста bind принимает любое число аргументов — они «прирастают» к функции слева. Получается новая функция, которой нужно передать только оставшиеся параметры. Это и называют частичным применением.
Если this функции не нужен, первым аргументом обычно ставят null — как в примере выше. А когда нужен и контекст, и заготовленные аргументы, bind закрывает оба вопроса одним вызовом.
Методы массива написаны так, что им достаточно числовых ключей и свойства length — знать, что перед ними настоящий массив, им не нужно. Значит, метод можно одолжить: взять его у Array.prototype и вызвать через call, подставив контекстом массивоподобный объект.
Классический случай — псевдомассив arguments внутри обычной функции: индексы есть, length есть, а методов массива нет. Раньше его превращали в массив через заимствованный slice; сегодня для этого есть Array.from и остаточные параметры, но старый приём вы встретите в любом легаси-коде.
- this у функции определяется в момент вызова, поэтому оторванный от объекта метод теряет контекст
- call и apply вызывают функцию сразу с указанным this и различаются только формой аргументов: список против массива
- bind ничего не вызывает — он возвращает новую функцию с намертво закреплённым контекстом, и перепривязать её нельзя
- Аргументы, переданные в bind после контекста, фиксируются заранее — это частичное применение
- Через call можно одолжить метод массива для массивоподобного объекта: ему хватает числовых ключей и length
1/3Чем call отличается от apply?
2/3Что возвращает вызов f.bind(obj)?
3/3Что выведет этот код? const f = function () { return this.n; }; const g = f.bind({ n: 1 }).bind({ n: 2 }); console.log(g());
Вернуть методу его объект
Метод total достали из объекта и положили в переменную — вместе с точкой пропал и контекст. Получите те же 300 через переменную total двумя способами: разовым вызовом с явным контекстом и созданием привязанной копии.
Разовый вызов — call (или apply: массив аргументов ему не обязателен, а здесь их и нет). Постоянная копия — bind: её можно передавать куда угодно, контекст останется при ней.
Обратите внимание: bind сам ничего не печатает — он только возвращает функцию, вызвать её нужно отдельно.
Одолжить методы для псевдомассива
У объекта list есть числовые ключи и length, но методов массива нет — list.join(", ") упадёт с ошибкой. Выведите строку 10, 20, 30, а затем получите настоящий массив и посчитайте сумму его элементов.
Берём методы у прототипа массива и подставляем list контекстом через call. Метод slice без аргументов копирует всё и возвращает уже полноценный массив — дальше можно пользоваться его методами напрямую.
Современная короткая запись того же самого — Array.from(list): она тоже делает из псевдомассива настоящий массив, но уже без заимствования.