Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Привязка функции к объекту и карринг: "bind/bindLate"

Лекция



Привет, сегодня поговорим про привязка функции к объекту, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое привязка функции к объекту, карринг, "bind/bindlate" , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).


  1. Привязка через замыкание
  2. Современный метод bind
    1. bind с аргументами
  3. Кросс-браузерная эмуляция bind
    1. Вариант bind c карринг ом
    2. Вариант bind для методов
  4. Итого

Функцию можно привязать к объекту, чтобы у нее всегда был один и тот же this, вне зависимости от контекста вызова.

Это удобно для передачи функции как параметра, чтобы не передавать вместе с ней ссылку на объект.

Для примера, рассмотрим потерю контекста, с которой мы сталкивались в главе про таймеры: вызовsetTimeout(user.sayHi, 1000) запустит функцию user.sayHi в глобальном контексте, не сохраняетthis:

 

   
01 function User() {
02   this.id = 1;
03  
04   this.sayHi = function() {
05     alert(this.id);
06   };
07 }
08  
09 var user = new User();
10  
11 setTimeout(user.sayHi, 1000); // выведет "undefined" (ожидается 1)

 

Проблема, конечно, не только в конкретном свойстве id, а в том, что, так как не передается this, из метода нельзя обратиться к другим свойствам объекта.

Самый простой способ это обойти — сделать вызов через обертку:

 

1 // анонимная фунция-обертка
2 setTimeout(function() {
3   user.sayHi();
4 }, 1000);

 

Но неужели это единственное решение? Что же теперь — при каждом подобном вызове оборачиватьuser.sayHi?

..Конечно, нет. Можно привязать контекст к функции, так что он будет всегда фиксирован. А как — мы сейчас увидим  

Привязка через замыкание

Самый простой способ привязать функцию к правильному this — это… Не использовать this!

Например, обращаться из функции к объекту через замыкание:

 

   
01 function User() {
02   this.id = 1;
03  
04   var self = this;  // сохранить this в замыкании
05  
06   this.sayHi = function() {
07     alert(self.id);
08   };
09 }
10  
11 var user = new User();
12  
13 setTimeout(user.sayHi, 1000); // выведет "1" (ура, работает)

 

Так как функция была «отучена» от this, то ее можно смело передавать куда угодно. Контекст будет правильным.

Современный метод bind

В современном JavaScript для привязки функций есть метод bind. Он поддерживается большинством современных браузеров, за исключением IE<9, но легко эмулируется.

Этот метод позволяет привязать функцию к нужному контексту и даже к аргументам.

Синтаксис bind:

var wrapper = func.bind(context[, arg1, arg2...])

 

func
Произвольная функция
wrapper
Функция-обертка, которую возвращает вызов bind. Она вызывает func, фиксируя контекст и, если указаны, первые аргументы.
context
Обертка wrapper будет вызывать функцию с контекстом this = context.
arg1arg2, …
Если указаны аргументы arg1, arg2... — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове.

Простейший пример, фиксируем только this:

 

1 function f() {
2   alert(this.name);
3 }
4  
5 var user = { name: "Вася" };
6  
7 var f2 = f.bind(user);
8  
9 f2(); // выполнит f с this = user

 

Использование в конструкторе, для привязки метода sayHi к создаваемому объекту:

 

   
01 function User() {
02   this.id = 1;
03  
04   this.sayHi = function() {
05     alert(this.id);
06   }.bind(this);
07 }
08  
09 var user = new User();
10  
11 setTimeout(user.sayHi, 1000); // выведет "1"

 

 

Важность: 4

Что выведет этот код?

 

1 function f() {
2   alert(this.name);
3 }
4  
5 f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } );
6  
7 f();

 

Решение

Ответ: "Вася".

 

   
1 function f() {
2   alert(this.name);
3 }
4  
5 f = f.bind( {name: "Вася"} ).bind( {name: "Петя"} );
6  
7 f(); // Вася

 

Первый вызов f.bind(..Вася..) возвращает «обертку», которая устанавливает контекст для f и передает вызов f.

Следующий вызов bind будет устанавливать контекст уже для этой обертки, это ни на что не влияет.

Чтобы это проще понять, используем наш собственный вариант bind вместо встроенного:

1 function bind(func, context) {
2   return function() {
3     return func.apply(context, arguments);
4   };
5 }

 

Код станет таким:

1 function f() {
2   alert(this.name);
3 }
4  
5 f = bind(f, {name: "Вася"} ); // (1)
6 f = bind(f, {name: "Петя"} ); // (2)
7  
8 f(); // Вася

 

Здесь видно, что первый вызов bind, в строке (1), возвращает обертку вокруг f, которая выглядит так (выделена):

1 function bind(func, context) {
2   return function() {
3     return func.apply(context, arguments);
4   };
5 }

 

В этой обертке нигде не используется this, только func и context. Об этом говорит сайт https://intellect.icu . Посмотрите на код, там нигде нет this.

Поэтому следующий bind в строке (2), который выполняется уже над оберткой и фиксирует в ней this, ни на что не влияет. Какая разница, что будет в качестве thisв функции, которая этот this не использует?

[Открыть задачу в новом окне]

 

bind с аргументами

Метод bind может создавать обертку, которая фиксирует не только контекст, но и ряд аргументов.

Например, есть функция перемножения mul(a, b):

 

function mul(a, b) {
  return a * b;
};

 

На ее основе мы можем создать функцию double, которая будет удваивать значения:

 

1 function mul(a, b) {
2   return a * b;
3 };
4  
5 // double умножает только на два
6 var double = mul.bind(null, 2); // первым аргументом всегда идет контекст
7  
8 alert( double(3) );  // 3*2 = 6
9 alert( double(4) );  // 4*2 = 8

 

Вызов mul.bind(null, 2) возвратил обертку, которая фиксирует контекст this = null и первый аргумент 2. Контекст в функциях не используется, поэтому не важно, чему он равен.

Получилась функция double = mul(2, *).

Так же можно создать triple, утраивающую значение:

 

var triple = mul.bind(null, 3);

 

 

Карринг

Создание новой функции путем фиксирования аргументов существующей «научно» называется карринг.

 

Для полноты картины рассмотрим сочетание обеих привязок: контекста и аргументов.

Пусть у объектов User есть метод send(to, message), который умеет посылать пользователю toсообщение message. Создадим функцию для отсылки сообщений Пете от Васи. Для этого нужно зафиксировать контекст и первый аргумент в send:

 

   
01 function User(name) {
02   this.toString = function() {
03     return name;
04   };
05  
06   this.send = function(to, message) {
07     alert( this ': ' + to + ', ' + message );
08   };
09 }
10  
11 var visitor = new User("Вася");
12 var admin = new User("Админ");
13  
14 // создать функцию для пересылки сообщений от admin к visitor
15 var sendFromAdminToVisitor = admin.send.bind(admin, visitor);
16  
17 sendFromAdminToVisitor('привет!');// Админ: Вася, привет!
18 sendFromAdminToVisitor('пока!');  // Админ: Вася, пока!

 

Зачем такая функция может быть нужна? Ну, например, для того чтобы передать ее в setTimeout или любое другое место программы, где может быть и про пользователей ничего не знают, а нужна какая-нибудь функция одного аргумента для сообщений. И такая вполне подойдет.

 

Кросс-браузерная эмуляция bind

Для IE<9 и старых версий других браузеров, которые не поддерживают bind, его можно реализовать самостоятельно.

Без поддержки карринга это очень просто.

Вот наша собственная функция привязки bind:

 

1 function bind(func, context) {
2   return function() {
3     return func.apply(context, arguments);
4   };
5 }

 

Ее использование:

   
01 function User() {
02   this.id = 1;
03  
04   this.sayHi = bind(function() {
05     alert(this.id);
06   }, this);
07 }
08  
09 var user = new User();
10  
11 setTimeout(user.sayHi, 1000); // выведет "1" 

 

Вариант bind c каррингом

Чтобы функция bind передавала аргументы, ее нужно «слегка» усложнить:

 

1 function bind(func, context /*, args*/) {
2   var bindArgs = [].slice.call(arguments, 2); // (1)
3   function wrapper() {                        // (2)
4     var args = [].slice.call(arguments);
5     var unshiftArgs = bindArgs.concat(args);  // (3)
6     return func.apply(context, unshiftArgs);  // (4)
7   }
8   return wrapper;
9 }

 

Страшновато выглядит, да?

Если интересно, работает так (по строкам):

  1. Вызов bind сохраняет дополнительные аргументы args (они идут со 2го номера) в массивbindArgs.
  2. … и возвращает обертку wrapper.
  3. Эта обертка делает из arguments массив args и затем, используя метод concat, прибавляет их к аргументам bindArgs (3).
  4. Затем передает вызов func (4).

Использование — в точности, как в примере выше, только вместо send.bind(admin, visitor)вызываем bind(send, admin, visitor).

Вариант bind для методов

Предыдущие версии bind привязывают любую функцию к любому объекту.

Но если нужно привязать к объекту не произвольную функцию, а ту, которая уже есть в объекте, т.е. его метод, то синтаксис можно упростить.

Обычный вызов bind:

var userMethod = bind(user.method, user);

 

Альтернативный синтаксис:

var userMethod = bind(user, 'method');

 

Поддержка этого синтаксиса легко встраивается в обычный bind. Для этого достаточно проверять типы первых аргументов:

 

 

Во фреймворках, как правило, есть свои методы привязок. Например, в jQuery это $.proxy, который работает как описано ранее:

 

var userMethod = $.proxy(user.method, user);
var userMethod = $.proxy(user, 'method');

 

…С другой стороны, редакторы, которые поддерживают автодополнение, не очень любят такие «оптимизации». Скажем, при попытке автоматизированного переименования method, они смогут найти его в вызове bind(user.method, user), но не смогут в bind(user, 'method').

Итого

Итоговый, укороченный, код bind для привязки функции или метода объекта:

01 function bind(func, context /*, args*/) {
02   var args = [].slice.call(arguments, 2);
03  
04   if (typeof context == "string") {   // bind(obj, 'method', ...)
05     args.unshift( func[context], func );
06     return bind.apply(this, args);
07   }
08  
09   return function() {
10     var unshiftArgs = args.concat( [].slice.call(arguments) );
11     return func.apply(context, unshiftArgs);
12   };
13  
14 }

 

Синтаксис: bind(func, context, аргументы) или bind(obj, 'method', аргументы).

Также можно использовать func.bind из современного JavaScript, при необходимости добавив кросс-браузерную эмуляцию библиотекой es5-shim:

К сожалению, в одной статье не просто дать все знания про привязка функции к объекту. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое привязка функции к объекту, карринг, "bind/bindlate" и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

создано: 2014-10-07
обновлено: 2024-11-14
356



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)