Function.prototype.bind()

Сводка

Метод bind() создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. В метод также передаётся набор аргументов, которые будут установлены перед переданными в привязанную функцию аргументами при её вызове.

Синтаксис

fun.bind(thisArg[, arg1[, arg2[, ...]]])

Параметры

thisArg
Значение, передаваемое в качестве this в целевую функцию при вызове привязанной функции. Значение игнорируется, если привязанная функция конструируется с помощью оператора new.
arg1, arg2, ...
Аргументы целевой функции, передаваемые перед аргументами привязанной функции при вызове целевой функции.

Описание

Метод bind() создаёт новую "привязанную функцию" (ПФ).  ПФ - это "необычный функциональный объект" ( термин из ECMAScript 6 ), который является обёрткой над исходным функциональным объектом. Вызов ПФ   приводит к исполнению кода обёрнутой функции.

ПФ имеет следующие внутренние ( скрытые ) свойства:

  • [[BoundTargetFunction]] - оборачиваемый  (целевой ) функциональный объект
  • [[BoundThis]] - значение, которое всегда передаётся в качестве значения   this при вызове обёрнутой функции.
  • [[BoundArguments]] - список значений, элементы которого используются в качестве первого аргумента при вызове оборачиваемой функции.
  • [[Call]] - внутренний метод. Выполняет код (функциональное выражение), связанный с функциональным объектом. 

Когда ПФ вызывается, исполняется её внутренний метод [[Call]] со следующими аргументами Call(target, boundThis, args).

  • target   -    [[BoundTargetFunction]];
  • boundThis   -    [[BoundThis]];
  • args  -   [[BoundArguments]].

Привязанная функция также может быть сконструирована с помощью оператора 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, со значением 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, либо частично реализованы [без поведения исключение-при-попытке-удаления], если реализация поддерживает расширения __defineGetter__ (en-US) и __defineSetter__ (en-US).)
  • Частичная реализация создаёт функции, имеющие свойство prototype. (Правильная привязанная функция его не имеет.)
  • Частичная реализация создаёт привязанные функции, чьё свойство length не соответствует с определением в ECMA-262; оно равно 0, в то время, как полная реализация, в зависимости от значения свойства length целевой функции и количества предопределённых аргументов, может вернуть значение, отличное от нуля.

Если вы решили использовать частичную реализацию, не рассчитывайте на корректную работу в тех случаях, когда реализация отклоняется от спецификации ECMA-262 5-го издания! Однако, в определённых случаях (и, возможно, с дополнительными модификациями для отдельных нужд), применение данной частичной реализации может быть вполне оправданным до тех пор, пока bind() не станет широко реализован в соответствии со спецификацией.

Спецификации

Спецификация Статус Комментарии
ECMAScript 5.1 (ECMA-262)
Определение 'Function.prototype.bind' в этой спецификации.
Стандарт Изначальное определение. Реализована в JavaScript 1.8.5.
ECMAScript 2015 (6th Edition, ECMA-262)
Определение 'Function.prototype.bind' в этой спецификации.
Стандарт  

Совместимость с браузерами

We're converting our compatibility data into a machine-readable JSON format. This compatibility table still uses the old format, because we haven't yet converted the data it contains. Find out how you can help! (en-US)
Возможность Chrome Firefox (Gecko) Internet Explorer Opera Safari
Базовая поддержка 7 4.0 (2) 9 11.60 5.1.4
Возможность Android Chrome для Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Базовая поддержка 4.0 0.16 4.0 (2) ? 11.50 6.0

На основе таблицы совместимости Kangax.

Смотрите также