Function.prototype.bind()

Метод bind() створює нову функцію, яка в момент виклику має певне присвоєне значення this, а також задану послідовність аргументів, що передують будь-яким аргументам, переданим під час виклику нової функції.

Синтаксис

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

Параметри

thisArg
Значення, що передаватиметься як параметр this до цільової функції, коли зв'язана функція викликається. Це значення ігнорується, якщо зв'язана функція утворена викликом оператора new. При використанні bind для створення функції (що подається для зворотного виклику) всередині setTimeout, будь-яке примітивне значення, що передається в якості thisArg, перетворюється на об'єкт. Якщо у bind не було передано жодних аргументів, this області видимості виконання передається як thisArg для нової функції.
arg1, arg2, ...
Аргументи, які будуть передувати аргументам, що були передані у зв'язану функцію, під час виклику цільової функції.

Значення, яке повертається

Копія заданої функції із заданим значенням this та початковими аргументами.

Опис

Функція bind() створює нову зв'язану функцію, яка є екзотичним об'єктом-функцією (термін з ECMAScript 2015), що огортає початкову функцію. Загалом, результатом виклику зв'язаної функції є виконання функції, яку вона огортає.

Зв'язана функція має наступні внутрішні властивості:

  • [[BoundTargetFunction]] - обгорнена функція;
  • [[BoundThis]] - значення, яке завжди передається як значення this, коли викликається обгорнена функція.
  • [[BoundArguments]] - список значень, елементи якого використовуються як перші аргументи будь-якого виклику обгорненої функції.
  • [[Call]] - виконує код, що пов'язаний з цим об'єктом. Запускається виразом виклику функції. Аргументами внутрішнього методу є значення this та список, що містить аргументи, передані у функцію виразом виклику.

Коли зв'язана функція викликається, вона викликає внутрішній метод [[Call]] на об'єкті [[BoundTargetFunction]] з наступними аргументами Call(boundThis, args). Де boundThis - це [[BoundThis]], а args - це [[BoundArguments]], за якими йдуть аргументи, передані викликом функції.

Зв'язана функція може також бути створена за допомогою оператора new: це працює так, якби замість неї створювалася б цільова функція. Передане значення this ігнорується, а передані аргументи передаються до імітованої функції.

Приклади

Створення зв'язаної функції

Найпростіше використання методу bind() - це зробити функцію, яка, незалежно від того, як вона викликається, буде викликатися з конкретним значенням this. Загальною помилкою початківців у JavaScript є витягнути метод з об'єкта, пізніше викликати цю функцію і очікувати, що вона буде використовувати початковий об'єкт в якості this (наприклад, використовуючи цей метод в коді на основі зворотних викликів). Однак, початковий об'єкт, якщо його не відслідковувати, зазвичай, втрачається. Створення зв'язаної функції з функції, яка використовує початковий об'єкт, елегантно вирішує цю проблему:

this.x = 9;    // тут значення this посилається на глобальний об'єкт переглядача "window"
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();   
// вертає 9 - Функція виконується в глобальній області видимості

// Створюємо нову функцію, прив'язуючи 'this' до об'єкта module
// Програмісти-новачки можуть переплутати
// глобальну змінну var x з властивістю x об'єкта module
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

Частково застосовані функції

Наступне найпростіше використання методу bind() - це зробити функцію з наперед визначеними початковими аргументами. Ці аргументи (якщо вони є) вказуються після наданого значення this і після цього передаються на початку списку аргументів, які передаються в дану функцію. За ними йдуть аргументи, передані у зв'язану функцію під час виклику.

function list() {
  return Array.prototype.slice.call(arguments);
}

function addArguments(arg1, arg2) {
    return arg1 + arg2
}

var list1 = list(1, 2, 3); // [1, 2, 3]

var result1 = addArguments(1, 2); // 3

// Створити функцію з попередньо заданим першим аргументом
var leadingThirtysevenList = list.bind(null, 37);

// Створити функцію з попередньо заданим першим аргументом.
var addThirtySeven = addArguments.bind(null, 37); 

var list2 = leadingThirtysevenList();
// [37]

var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]

var result2 = addThirtySeven(5); 
// 37 + 5 = 42 

var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42, другий аргумент ігнорується

З setTimeout

За замовчуванням, всередині window.setTimeout() значенням this буде встановлено об'єкт window (або global). Працюючи з методами класу, що вимагають this для посилання на екземпляри класу, ви можете явно прив'язати this до функції зворотного виклику, щоб зберегти екземпляр.

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Запустити declare після затримки у 1 секунду
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('Я чудова квітка з ' +
    this.petalCount + ' пелюстками!');
};

var flower = new LateBloomer();
flower.bloom();  
// через 1 секунду запускається метод 'declare'

Зв'язані функції в ролі конструкторів

Застереження: Цей розділ демонструє можливості 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'

// не підтримується у поліфілі, що наведений нижче

// працює добре з рідним методом bind:

var YAxisPoint = Point.bind(null, 0/*x*/);


var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 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.apply(arguments);

З bind() це можна спростити. В наступній частині коду, slice є зв'язаною функцією з функцією apply() об'єкта Function.prototype, зі значенням this, яке дорівнює функції slice() об'єкта Array.prototype. Це означає що додаткові виклики методу apply() можуть бути усунені:

// те саме, що й "slice" у попередньому прикладі
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

slice(arguments);

Поліфіл

Оскільки старші переглядачі, загалом, є також повільнішими, набагато критичніше, ніж багатьом здається, створювати продуктивні поліфіли, щоб користування переглядачем не було таким жахливим. Отже, нижче наведені два варіанти поліфілів Function.prototype.bind. Перший набагато менший та спритніший, але не працює при використанні оператора `new`. Другий менш продуктивний та більший за розміром, але дозволяє певне використання оператора `new` на зв'язаних функціях. Загалом, у коді рідко можна зустріти `new`, що використовується на зв'язаній функції, тому, в цілому, краще обирати перший варіант.

// Не працює з `new funcA.bind(thisArg, args)`
if (!Function.prototype.bind) (function(){
  var slice = Array.prototype.slice;
  Function.prototype.bind = function() {
    var thatFunc = this, thatArg = arguments[0];
    var args = slice.call(arguments, 1);
    if (typeof thatFunc !== 'function') {
      // найближчий можливий варіант до внутрішньої
      // функції IsCallable у ECMAScript 5
      throw new TypeError('Function.prototype.bind - ' +
             'what is trying to be bound is not callable');
    }
    return function(){
      var funcArgs = args.concat(slice.call(arguments))
      return thatFunc.apply(thatArg, funcArgs);
    };
  };
})();

Ви можете це частково обійти, вставивши наступний код на початку ваших скриптів, що дозволить використовувати більшу частину функціональності bind() у реалізаціях, які початково її не підтримують.

// Так, це працює з `new funcA.bind(thisArg, args)`
if (!Function.prototype.bind) (function(){
  var ArrayPrototypeSlice = Array.prototype.slice;
  Function.prototype.bind = function(otherThis) {
    if (typeof this !== 'function') {
      // найближчий можливий варіант до внутрішньої
      // функції IsCallable у ECMAScript 5
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var baseArgs= ArrayPrototypeSlice .call(arguments, 1),
        baseArgsLength = baseArgs.length,
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          baseArgs.length = baseArgsLength; // скинути до початкових базових аргументів
          baseArgs.push.apply(baseArgs, arguments);
          return fToBind.apply(
                 fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
          );
        };

    if (this.prototype) {
      // Function.prototype не має властивості prototype
      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 на get, set чи видалення. (Це можна додати, якщо реалізація підтримує Object.defineProperty, або частково реалізувати [без викидання винятків на видалення], якщо реалізація підтримує розширення __defineGetter__ та __defineSetter__.)
  • Часткова реалізація створює функції, які мають властивість prototype. (Правильні зв'язані функції її не мають.)
  • Часткова реалізація створює зв'язані функції, чия властивість length не узгоджується з встановленою у ECMA-262: вона створює функції зі значенням length 0, в той час, як повна реалізація, в залежності від значення length цільової функції та числа попередньо заданих аргументів, може повернути ненульове значення.

Якщо ви вирішили використовувати цю часткову реалізацію, ви не повинні покладатися на неї у ситуаціях, де її поведінка відрізняється від 5-ї версії ECMA-262! На щастя, ці відхилення від специфікації не часто (якщо взагалі) зустрічаються у більшості випадків кодування. Якщо ви не розумієте жодних відхилень від специфікації, наведених вище, тоді, у цьому конкретному випадку, буде безпечно не хвилюватись щодо подробиць цих невідповідностей.

Якщо це абсолютно необхідно, а продуктивність неважлива, значно повільніше рішення (але у більшій відповідності до специфікації) можна знайти тут https://github.com/Raynos/function-bind.

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

Специфікація Статус Коментар
ECMAScript 5.1 (ECMA-262)
The definition of 'Function.prototype.bind' in that specification.
Standard Початкове визначення. Реалізовано у 1.8.5.
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Function.prototype.bind' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'Function.prototype.bind' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
bindChrome Full support 7Edge Full support 12Firefox Full support 4IE Full support 9Opera Full support 11.6Safari Full support 5.1WebView Android Full support 4Chrome Android Full support 18Firefox Android Full support 4Opera Android Full support 12Safari iOS Full support 6Samsung Internet Android Full support 1.0nodejs Full support Yes

Legend

Full support  
Full support

Дивіться також