Binding a function to an object and carring: "bind / bindLate"

Lecture




  1. Snap through closure
  2. Modern bind method
    1. bind with arguments
  3. bind cross-browser emulation
    1. Variant bind with currying
    2. Option bind for methods
  4. Total

A function can be bound to an object so that it always has the same this , regardless of the context of the call.

This is convenient for passing a function as a parameter, so as not to transmit an object reference with it.

For example, consider the loss of context that we encountered in the chapter on timers: calling setTimeout(user.sayHi, 1000) will run the user.sayHi function in a global context, does not save 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)

The problem, of course, is not only in the specific id property, but in the fact that, since this is not passed, you cannot refer to other properties of the object from the method.

The easiest way to get around this is to make a call through a wrapper:

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

But is this the only solution? What now - with each such call to wrap user.sayHi ?

..Of course not. You can bind the context to a function, so that it will always be fixed. And how - we will see

Snap through closure

The easiest way to bind a function to the correct this is ... Do not use this !

For example, to access an object from a function via a closure:

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" (ура, работает)

Since the function was “weaned off” from this , it can be safely passed anywhere. The context will be correct.

Modern bind method

In modern JavaScript, there is a bind method for binding functions. It is supported by most modern browsers, with the exception of IE <9, but is easily emulated.

This method allows you to bind a function to the desired context and even to arguments.

bind syntax:

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

func
Arbitrary function
wrapper
The wrapper function returned by the bind call. It calls func , fixing the context and, if specified, the first arguments.
context
The wrapper will call the function with the context this = context .
arg1 , arg2 , ...
If the arguments arg1, arg2... are specified - they will be added to each call of a new function, and they will be faced with those specified in the call.

The simplest example, we fix only 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

Use in the constructor to bind the sayHi method to the object being created:

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"

Importance: 4

What will this code output?

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

Decision

Answer: "Вася" . "Вася"

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

The first call to f.bind(..Вася..) returns a "wrapper" that sets the context for f and passes the call to f .

The next call to bind will set the context already for this wrapper, this does not affect anything.

To make this easier to understand, use our own version of bind instead of the built-in one:

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

The code will be as follows:

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

Here you can see that the first call to bind , in line (1) , returns a wrapper around f , which looks like this (highlighted):

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

In this wrapper, this is not used anywhere else, only func and context . Look at the code, there is no this anywhere.

Therefore, the next bind in line (2) , which is already running on the wrapper and fixes this in it, does not affect anything. What difference does it make as this in a function that does not use this this ?

[Open task in new window]

bind with arguments

The bind method can create a wrapper that captures not only the context, but also a number of arguments.

For example, there is a multiplication function mul(a, b) :

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

Based on it, we can create a double function that will double the values:

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

The call to mul.bind(null, 2) returned a wrapper that captures the context this = null and the first argument 2 . The context is not used in functions, so it does not matter what it is equal to.

The function is double = mul(2, *) .

You can also create a triple , triple value:

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

Carring

Creating a new function by fixing the arguments of an existing “scientific” is called currying.

For completeness, consider a combination of both bindings: context and arguments.

Let User objects have a send(to, message) method that can send a message to user. Let's create a function for sending messages to Petya from Vasya. To do this, we need to fix the context and the first argument to 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( 'пока!' ); // Админ: Вася, пока!

Why such a function may be needed? Well, for example, in order to pass it to setTimeout or any other place in the program, where it may be that users do not know anything, but need some function of one argument for messages. And this is quite suitable.

bind cross-browser emulation

For IE <9 and older versions of other browsers that do not support bind , you can implement it yourself.

Without the support of currying, it's very simple.

Here is our own bind bind function:

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

Its use:

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"

Variant bind with currying

In order for the bind function to pass arguments, it needs to be "slightly" complicated:

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 }

Scary looks, right?

If interested, it works like this (in lines):

  1. Calling bind saves additional args arguments (they come from the 2nd number) to the bindArgs array.
  2. ... and returns the wrapper wrapper .
  3. This wrapper makes the arguments array from arguments and then, using the concat method, adds them to the bindArgs (3) arguments.
  4. It then passes the call to func (4) .

The use is exactly as in the example above, only instead of send.bind(admin, visitor) call bind(send, admin, visitor) .

Option bind for methods

Previous versions of bind bind any function to any object.

But if you want to bind to an object not an arbitrary function, but one that is already in the object, i.e. its method, the syntax can be simplified.

Normal bind call:

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

Alternative syntax:

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

Support for this syntax is easily embedded in a regular bind . To do this, it suffices to check the types of the first arguments:

In frameworks, as a rule, there are methods of bindings. For example, in jQuery this is $ .proxy, which works as described earlier:

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

... On the other hand, editors who support autocompletion do not like such “optimizations” very much. Say, if you try to automatically rename a method , they will be able to find it in the bind(user.method, user) call bind(user.method, user) , but they will not be able to in the bind(user, 'method') .

Total

Summary, shortened, bind code for binding a function or an object method:

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 }

Syntax: bind(func, context, аргументы) or bind(obj, 'method', аргументы) .

You can also use func.bind from modern JavaScript, if necessary, adding cross-browser emulation by the es5-shim library:

created: 2014-10-07
updated: 2021-01-10
132614



Rating 9 of 10. count vote: 2
Are you satisfied?:



Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone