Метод bind
Метод bind привязать контекст к функции. В качестве первого параметра следует передавать контекст, а последующими параметрами — параметры функции. Метод возвращает новую функцию, внутри которой this будет равным переданному контексту.
Синтаксис
функция.bind(контекст, параметр1, параметр2. );
Пример
Пусть у нас есть инпут:
Пусть ссылка на этот инпут записана в переменную elem :
let elem = document.querySelector(‘#elem’);
Пусть у нас также есть следующая функция func :
function func(param1, param2) < console.log(this.value + param1 + param2); >
Давайте с помощью bind сделаем новую функцию, которая будет копией функции func , но this в ней всегда будет равен elem :
let newFunc = func.bind(elem);
Теперь в переменной newFunc лежит функция. Давайте вызовем ее, передав в первый параметр ‘1’ , а во второй ‘2’ :
newFunc(‘1’, ‘2’);
Давайте соберем все вместе:
let elem = document.getElementById(‘elem’); function func(param1, param2) < console.log(this.value + param1 + param2); >let newFunc = func.bind(elem); newFunc(‘1’, ‘2’); // выведет ‘text12’
Пример
Не обязательно записывать результат работы bind в новую функцию newFunc , можно просто перезаписать func :
let func = func.bind(elem);
Смотрите также
- метод call ,
который вызывает функцию с контекстом - метод apply ,
который вызывает функцию с контекстом
Привязка контекста к функции
При передаче методов объекта в качестве колбэков, например для setTimeout , возникает известная проблема – потеря this .
В этой главе мы посмотрим, как её можно решить.
Потеря «this»
Мы уже видели примеры потери this . Как только метод передаётся отдельно от объекта – this теряется.
Вот как это может произойти в случае с setTimeout :
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(user.sayHi, 1000); // Привет, undefined!
При запуске этого кода мы видим, что вызов this.firstName возвращает не «Вася», а undefined !
Это произошло потому, что setTimeout получил функцию sayHi отдельно от объекта user (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как:
let f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли
Метод setTimeout в браузере имеет особенность: он устанавливает this=window для вызова функции (в Node.js this становится объектом таймера, но здесь это не имеет значения). Таким образом, для this.firstName он пытается получить window.firstName , которого не существует. В других подобных случаях this обычно просто становится undefined .
Задача довольно типичная – мы хотим передать метод объекта куда-то ещё (в этом конкретном случае – в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
Решение 1: сделать функцию-обёртку
Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(function() < user.sayHi(); // Привет, Вася! >, 1000);
Теперь код работает корректно, так как объект user достаётся из замыкания, а затем вызывается его метод sayHi .
То же самое, только короче:
setTimeout(() => user.sayHi(), 1000); // Привет, Вася!
Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость.
Что произойдёт, если до момента срабатывания setTimeout (ведь задержка составляет целую секунду!) в переменную user будет записано другое значение? Тогда вызов неожиданно будет совсем не тот!
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; setTimeout(() => user.sayHi(), 1000); // . в течение 1 секунды user = < sayHi() < alert("Другой пользователь в 'setTimeout'!"); >>; // Другой пользователь в 'setTimeout'!
Следующее решение гарантирует, что такого не случится.
Решение 2: привязать контекст с помощью bind
В современном JavaScript у функций есть встроенный метод bind, который позволяет зафиксировать this .
Базовый синтаксис bind :
// полный синтаксис будет представлен немного позже let boundFunc = func.bind(context);
Результатом вызова func.bind(context) является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в func , при этом устанавливая this=context .
Другими словами, вызов boundFunc подобен вызову func с фиксированным this .
Например, здесь funcUser передаёт вызов в func , фиксируя this=user :
let user = < firstName: "Вася" >; function func() < alert(this.firstName); >let funcUser = func.bind(user); funcUser(); // Вася
Здесь func.bind(user) – это «связанный вариант» func , с фиксированным this=user .
Все аргументы передаются исходному методу func как есть, например:
let user = < firstName: "Вася" >; function func(phrase) < alert(phrase + ', ' + this.firstName); >// привязка this к user let funcUser = func.bind(user); funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user)
Теперь давайте попробуем с методом объекта:
let user = < firstName: "Вася", sayHi() < alert(`Привет, $!`); > >; let sayHi = user.sayHi.bind(user); // (*) sayHi(); // Привет, Вася! setTimeout(sayHi, 1000); // Привет, Вася!
В строке (*) мы берём метод user.sayHi и привязываем его к user . Теперь sayHi – это «связанная» функция, которая может быть вызвана отдельно или передана в setTimeout (контекст всегда будет правильным).
Здесь мы можем увидеть, что bind исправляет только this , а аргументы передаются как есть:
let user = < firstName: "Вася", say(phrase) < alert(`$, $!`); > >; let say = user.say.bind(user); say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say") say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say")
Удобный метод: bindAll
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
for (let key in user) < if (typeof user[key] == 'function') < user[key] = user[key].bind(user); >>
Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например _.bindAll(obj) в lodash.
Частичное применение
До сих пор мы говорили только о привязывании this . Давайте шагнём дальше.
Мы можем привязать не только this , но и аргументы. Это делается редко, но иногда может быть полезно.
Полный синтаксис bind :
let bound = func.bind(context, [arg1], [arg2], . );
Это позволяет привязать контекст this и начальные аргументы функции.
Например, у нас есть функция умножения mul(a, b) :
function mul(a, b)
Давайте воспользуемся bind , чтобы создать функцию double на её основе:
function mul(a, b) < return a * b; >let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10
Вызов mul.bind(null, 2) создаёт новую функцию double , которая передаёт вызов mul , фиксируя null как контекст, и 2 – как первый аргумент. Следующие аргументы передаются как есть.
Это называется частичное применение – мы создаём новую функцию, фиксируя некоторые из существующих параметров.
Обратите внимание, что в данном случае мы на самом деле не используем this . Но для bind это обязательный параметр, так что мы должны передать туда что-нибудь вроде null .
В следующем коде функция triple умножает значение на три:
function mul(a, b) < return a * b; >let triple = mul.bind(null, 3); alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15
Для чего мы обычно создаём частично применённую функцию?
Польза от этого в том, что возможно создать независимую функцию с понятным названием ( double , triple ). Мы можем использовать её и не передавать каждый раз первый аргумент, т.к. он зафиксирован с помощью bind .
В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант.
Например, у нас есть функция send(from, to, text) . Потом внутри объекта user мы можем захотеть использовать её частный вариант: sendTo(to, text) , который отправляет текст от имени текущего пользователя.
Частичное применение без контекста
Что если мы хотим зафиксировать некоторые аргументы, но не контекст this ? Например, для метода объекта.
Встроенный bind не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.
К счастью, легко создать вспомогательную функцию partial , которая привязывает только аргументы.
function partial(func, . argsBound) < return function(. args) < // (*) return func.call(this, . argsBound, . args); >> // использование: let user = < firstName: "John", say(time, phrase) < alert(`[$] $: $!`); > >; // добавляем частично применённый метод с фиксированным временем user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Что-то вроде этого: // [10:00] John: Hello!
Результатом вызова partial(func[, arg1, arg2. ]) будет обёртка (*) , которая вызывает func с:
- Тем же this , который она получает (для вызова user.sayNow – это будет user )
- Затем передаёт ей . argsBound – аргументы из вызова partial ( «10:00» )
- Затем передаёт ей . args – аргументы, полученные обёрткой ( «Hello» )
Благодаря оператору расширения . реализовать это очень легко, не правда ли?
Также есть готовый вариант _.partial из библиотеки lodash.
Итого
Метод bind возвращает «привязанный вариант» функции func , фиксируя контекст this и первые аргументы arg1 , arg2 …, если они заданы.
Обычно bind применяется для фиксации this в методе объекта, чтобы передать его в качестве колбэка. Например, для setTimeout .
Когда мы привязываем аргументы, такая функция называется «частично применённой» или «частичной».
Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция send(from, to) и from всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней.
Function.prototype.bind()
Метод bind() создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. В метод также передаётся набор аргументов, которые будут установлены перед переданными в привязанную функцию аргументами при её вызове.
Синтаксис
fun.bind(thisArg[, arg1[, arg2[, . ]]])
Параметры
Значение, передаваемое в качестве this в целевую функцию при вызове привязанной функции. Значение игнорируется, если привязанная функция конструируется с помощью оператора new .
Аргументы целевой функции, передаваемые перед аргументами привязанной функции при вызове целевой функции.
Описание
Метод bind() создаёт новую «привязанную функцию» (ПФ). ПФ — это «необычный функциональный объект» ( термин из ECMAScript 6 ), который является обёрткой над исходным функциональным объектом. Вызов ПФ приводит к исполнению кода обёрнутой функции.
ПФ имеет следующие внутренние ( скрытые ) свойства:
- [[BoundTargetFunction]] — оборачиваемый (целевой ) функциональный объект
- [[BoundThis]] — значение, которое всегда передаётся в качестве значения this при вызове обёрнутой функции.
- [[BoundArguments]] — список значений, элементы которого используются в качестве первого аргумента при вызове оборачиваемой функции.
- [[Call]] — внутренний метод. Выполняет код (функциональное выражение), связанный с функциональным объектом.
Когда ПФ вызывается, исполняется её внутренний метод [[Call]] со следующими аргументами Call(target, boundThis, args).
Привязанная функция также может быть сконструирована с помощью оператора new : это работает так, как если бы вместо неё конструировалась целевая функция. Предоставляемое значение this в этом случае игнорируется, хотя ведущие аргументы всё ещё передаются в эмулируемую функцию.
Примеры
Пример: создание привязанной функции
Простейшим способом использования bind() является создание функции, которая, вне зависимости от способа её вызова, вызывается с определённым значением this . Обычным заблуждением для новичков в JavaScript является извлечение метода из объекта с целью его дальнейшего вызова в качестве функции и ожидание того, что он будет использовать оригинальный объект в качестве своего значения this (например, такое может случиться при использовании метода как колбэк-функции). Однако, без специальной обработки, оригинальный объект зачастую теряется. Создание привязанной функции из функции, использующей оригинальный объект, изящно решает эту проблему:
this.x = 9; var module = x: 81, getX: function () return this.x; >, >; module.getX(); // 81 var getX = module.getX; getX(); // 9, поскольку в этом случае this ссылается на глобальный объект // создаём новую функцию с this, привязанным к module var boundGetX = getX.bind(module); boundGetX(); // 81
Пример: частичные функции
Следующим простейшим способом использования bind() является создание функции с предопределёнными аргументами. Эти аргументы (если они есть) передаются после значения this и вставляются перед аргументами, передаваемыми в целевую функцию при вызове привязанной функции.
function list() return Array.prototype.slice.call(arguments); > var list1 = list(1, 2, 3); // [1, 2, 3] // Создаём функцию с предустановленным ведущим аргументом var leadingThirtysevenList = list.bind(undefined, 37); var list2 = leadingThirtysevenList(); // [37] var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
Пример: с setTimeout
По умолчанию, внутри window.setTimeout() (en-US) контекст this устанавливается в объект window (или global ). При работе с методами класса, требующими this для ссылки на экземпляры класса, вы можете явно привязать this к колбэк-функции для сохранения экземпляра.
function LateBloomer() this.petalCount = Math.ceil(Math.random() * 12) + 1; > // Объявляем цветение с задержкой в 1 секунду LateBloomer.prototype.bloom = function () window.setTimeout(this.declare.bind(this), 1000); >; LateBloomer.prototype.declare = function () console.log("Я прекрасный цветок с " + this.petalCount + " лепестками!"); >;
Пример: привязывание функций, используемых в качестве конструкторов
Предупреждение: этот раздел демонстрирует возможности JavaScript и документирует некоторые граничные случаи использования метода bind() . Показанные ниже методы не являются лучшей практикой и, вероятно, их не следует использовать в рабочем окружении.
Привязанные функции автоматически подходят для использования вместе с оператором new для конструирования новых экземпляров, создаваемых целевой функцией. Когда привязанная функция используется для конструирования значения, предоставляемое значение this игнорируется. Однако, предоставляемые аргументы всё так же вставляются перед аргументами конструктора:
function Point(x, y) this.x = x; this.y = y; > Point.prototype.toString = function () return this.x + "," + this.y; >; var p = new Point(1, 2); p.toString(); // '1,2' var emptyObj = >; var YAxisPoint = Point.bind(emptyObj, 0 /*x*/); // не поддерживается полифилом, приведённым ниже, // но отлично работает с родным bind: var YAxisPoint = Point.bind(null, 0 /*x*/); var axisPoint = new YAxisPoint(5); axisPoint.toString(); // '0,5' axisPoint instanceof Point; // true axisPoint instanceof YAxisPoint; // true new Point(17, 42) instanceof YAxisPoint; // true
Обратите внимание, что вам не нужно делать ничего особенного для создания привязанной функции, используемой с оператором new . В итоге, для создания явно вызываемой привязанной функции, вам тоже не нужно делать ничего особенного, даже если вам требуется, чтобы привязанная функция вызывалась только с помощью оператора new .
// Пример может быть запущен прямо в вашей консоли JavaScript // . продолжение примера выше // Всё ещё можно вызывать как нормальную функцию // (хотя обычно это не предполагается) YAxisPoint(13); emptyObj.x + "," + emptyObj.y; // > '0,13'
Если вы хотите поддерживать использование привязанной функции только с помощью оператора new , либо только с помощью прямого вызова, целевая функция должна предусматривать такие ограничения.
Пример: создание сокращений
Метод bind() также полезен в случаях, если вы хотите создать сокращение для функции, требующей определённое значение this .
Возьмём, например, метод Array.prototype.slice , который вы можете использовать для преобразования массивоподобного объекта в настоящий массив. Вы можете создать подобное сокращение:
var slice = Array.prototype.slice; // . slice.call(arguments);
С помощью метода bind() , это сокращение может быть упрощено. В следующем куске кода slice является функцией, привязанной к функции call() объекта Function.prototype (en-US), со значением this , установленным в функцию slice() объекта Array.prototype . Это означает, что дополнительный вызов call() может быть устранён:
// Тоже самое, что и slice в предыдущем примере var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); // . slice(arguments);
Полифил
Функция bind является дополнением к стандарту ECMA-262 5-го издания; поэтому она может присутствовать не во всех браузерах. Вы можете частично обойти это ограничение, вставив следующий код в начало ваших скриптов, он позволяет использовать большую часть возможностей bind() в реализациях, не имеющих его родной поддержки.
if (!Function.prototype.bind) Function.prototype.bind = function (oThis) if (typeof this !== "function") // ближайший аналог внутренней функции // IsCallable в ECMAScript 5 throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable", ); > var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () >, fBound = function () return fToBind.apply( this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)), ); >; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; >; >
Некоторые из многих отличий (так же могут быть и другие, данный список далеко не исчерпывающий) между этой реализацией и реализацией по умолчанию:
- Частичная реализация предполагает, что методы Array.prototype.slice() , Array.prototype.concat() , Function.prototype.call() и Function.prototype.apply() являются встроенными, имеют своё первоначальное значение.
- Частичная реализация создаёт функции, не имеющие неизменяемых свойств «отравленной пилюли» — caller и arguments — которые выбрасывают исключение TypeError при попытке получить, установить или удалить эти свойства. (Такие свойства могут быть добавлены, если реализация поддерживает Object.defineProperty , либо частично реализованы [без поведения исключение-при-попытке-удаления], если реализация поддерживает расширения Object.prototype.__defineGetter__() и Object.prototype.__defineSetter__() .)
- Частичная реализация создаёт функции, имеющие свойство prototype . (Правильная привязанная функция его не имеет.)
- Частичная реализация создаёт привязанные функции, чьё свойство length не соответствует с определением в ECMA-262; оно равно 0, в то время, как полная реализация, в зависимости от значения свойства length целевой функции и количества предопределённых аргументов, может вернуть значение, отличное от нуля.
Если вы решили использовать частичную реализацию, не рассчитывайте на корректную работу в тех случаях, когда реализация отклоняется от спецификации ECMA-262 5-го издания! Однако, в определённых случаях (и, возможно, с дополнительными модификациями для отдельных нужд), применение данной частичной реализации может быть вполне оправданным до тех пор, пока bind() не станет широко реализован в соответствии со спецификацией.
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-function.prototype.bind |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Function.prototype.apply()
- Function.prototype.call()
- Функции и их область видимости (en-US)
Метод функций bind своими руками
Чтобы указать контекст выполнения любой функций вы можете использовать три метода: call , apply и bind . При использовании первых двух методов происходит вызов функции “на месте”, метод bind функцию не вызывает, вместо этого он возвращает новую функцию с заданным контекстом. Разберёмся в простом примере, чтобы понять, как работает метод bind :
var sum = function(a, b) return this.sum + a + b; >;
Изначально, при запуске в глобальной области видимости функция будет использовать объект window в качестве контекста, поэтому если попробовать её вызвать без задания контекста, вы получите не самый очевидный рельтат:
// Вызов функции, контекстом является window sum(1, 2); // Результат выполнения "function (a, b) < return this.sum + a + b; >12"
При инициализации переменной sum с помощью оператора var происходит запись анонимной функции в глобальную область видимости (объект window в браузере). Именно поэтому результатом выполнения функции является такое странное значение. Работает это следующим образом:
- При создании переменной sum в глобальной области видимости в объект window записывается соответствующее свойство.
- При выполнении функции контекстом является объект window, то есть this ссылается на window.
- При использовании оператора сложения будет произведено автоматическое приведение типов
Чтобы указать функции sum контекст выполнения воспользуемся метод bind:
var bindedSum = sum.bind( sum: 10 >); bindedSum(1, 2); // 13
Метод bind вернул новую функцию, привязанную к контексту обозначенного объекта. Сама функция sum при этом не была изменена.
Но это ещё не всё, что может предложить bind. При создании новой функции также можно указать неограниченное количество аргументов, с которыми данная функция будет вызвана. Чтобы лучше проиллюстрировать этот и дальнейшие примеры перепишем функцию sum так, чтобы она складывала неограниченное количество аргументов:
var sum = function() return [].reduce.call(arguments, function(result, current) return result + current; >, this.sum); >; // Привязка контекста и нескольких аргуметов var bindedSum = sum.bind( sum: 10 >, 20, 30); bindedSum(40, 50); // 10 + 20 + 30 + 40 + 50 === 150
Подобным образом мы можем передавать любое количество аргументов в sum, как на этапе привязки к контексту, так и при вызове новой функций.
Bind своими руками
Вы получили хитрое задание: вам необходимо реализовать метод bind самостоятельно в виде функции, сам метод bind, естественно, использовать нельзя. Не паникуйте, у вас ещё остались методы call и apply. Для начала, обозначим себе три правила:
- Функция bind принимает неограниченное количество аргументов, но первый и второй отвечают за саму функцию, которой будет задаваться контекст, и сам контекст соответственно. Все остальные агрументы должны быть переданы в функцию.
- Функция bind не вызывает передаваемую ей функцию, а лишь задаёт для неё контекст.
- Получаемой при помощи bind функции можно передать неограниченное количество аргументов, которые будут использованы по мере надобности.
var bind = function(fn, context) // обрезаем ненужные аргументы (функцию и контекст) var bindArgs = [].slice.call(arguments, 2); return function() // здесь все аргументы будут необходимы var fnArgs = [].slice.call(arguments); // собираем все return fn.apply(context, bindArgs.concat(fnArgs)); >; >;
Написанная выше функция bind работает точно также, как и метод bind:
var bindedSum = bind(sum, sum: 10>, 20, 30); bindedSum(40, 50, 60); // 210