Перейти к содержимому

Bind js что это

  • автор:

Метод 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 в браузере). Именно поэтому результатом выполнения функции является такое странное значение. Работает это следующим образом:

  1. При создании переменной sum в глобальной области видимости в объект window записывается соответствующее свойство.
  2. При выполнении функции контекстом является объект window, то есть this ссылается на window.
  3. При использовании оператора сложения будет произведено автоматическое приведение типов

Чтобы указать функции 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. Для начала, обозначим себе три правила:

  1. Функция bind принимает неограниченное количество аргументов, но первый и второй отвечают за саму функцию, которой будет задаваться контекст, и сам контекст соответственно. Все остальные агрументы должны быть переданы в функцию.
  2. Функция bind не вызывает передаваемую ей функцию, а лишь задаёт для неё контекст.
  3. Получаемой при помощи 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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *