📜 ⬆️ ⬇️

Context binding (this) to a function in javascript and partial application of functions

In the previous post I described that this in javascript is not bound to an object, but depends on the context of the call. In practice, it is often necessary that the inside of a function always refers to a specific object.
In this article we will consider two approaches for solving this problem.
1. jQuery.proxy - approach using the popular jQuery library
2. Function.prototype.bind is an approach added in JavaScript 1.8.5. We will also consider its use for currying (partial application of the function) and some subtleties of work that units know about.


Introduction


Consider a simple object containing a property x and a method f that outputs the value of this.x to the console .
var object = { x: 3, f: function() { console.log(this.x); } } 

As I indicated in the previous post , when calling object.f () , the number 3 will be displayed in the console. Suppose now that we need to output this number in 1 second.
 setTimeout(object.f, 1000); //  undefined //    —    : setTimeout(function() { object.f(); }, 1000); //  3 

Using the wrapper function every time is inconvenient. We need a way to bind the context of the function, so that this within the object.f function always refers to object

1. jQuery.proxy


 jQuery.proxy(function, context); jQuery.proxy(context, name); 

It's no secret that jQuery is a very popular javascript library, so first we will look at using jQuery.proxy to bind context to a function.
jQuery.proxy returns a new function that, when invoked, calls the original function function in the context . Using jQuery.proxy, the above task can be solved like this:
 setTimeout($.proxy(object.f, object), 1000); //  3 

If we need to specify the same callback several times, instead of duplicating
 setTimeout($.proxy(object.f, object), 1000); setTimeout($.proxy(object.f, object), 2000); setTimeout($.proxy(object.f, object), 3000); 

it is better to bring the result of $ .proxy to a separate variable
 var fn = $.proxy(object.f, object); setTimeout(fn, 1000); setTimeout(fn, 2000); setTimeout(fn, 3000); 

')
Let us now pay attention to the fact that we have twice specified the object inside $. Proxy (the first time the object's method is object.f , the second is the passed context, object ). Maybe there is a possibility to avoid duplication? The answer is yes. For such cases, an alternative possibility of passing parameters is added to $. Proxy - the first parameter must be an object, and the second is the name of its method. Example:
 var fn = $.proxy(object, "f"); setTimeout(fn, 1000); 

Note that the name of the method is passed as a string.

2. Function.prototype.bind


 func.bind(context[, arg1[, arg2[, ...]]]) 


Let's move on to reviewing Function.prototype.bind. This method was added in JavaScript 1.8.5.
Browser Compatibility
Firefox (Gecko): 4.0 (2)
Chrome: 7
Internet Explorer: 9
Opera: 11.60
Safari: 5.1.4

Emulation Function.prototype.bind from the Mozilla Developer Network
 Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function 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; }; 


Function.prototype.bind has 2 functions - static context binding to a function and partial application of the function.
Basically, bind creates a new function that calls func in context. 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 of the new function.

2.1. Context binding

Using bind to bind context is very simple, just consider an example:
 function f() { console.log(this.x); } var bound = f.bind({x: 3}); // bound -   - "",   this    {x:3} bound();//  3 


Thus, the example from the introduction can be written as follows:
 var object = { x: 3, f: function() { console.log(this.x); } } setTimeout(object.f.bind(object), 1000); //  3 


2.2. Partial application of functions

For simplicity, let's take a look at an example of using bind to partially apply functions.
 function f(x, y, z) { console.log(x + y + z); } var bound = f.bind(null, 3, 5); //     -    ,     this   f,      -      null bound(7); //  15 (3 + 5 + 7) bound(17); //  25 (3 + 5 + 17) 


As you can see from the example - the essence of the partial application of functions is simple - creating a new function with a reduced number of arguments, by “fixing” the first arguments with the bind function.
This would end the article, but ... Functions obtained using the bind method have some peculiarities in behavior.

2.3. Bind features

In the comments to the previous article 2 examples were given concerning bind ( one , two ).
I decided to make a mix of these examples, simultaneously changing the string values ​​to make it easier to deal with them.
Example (try to guess the answers)
 function ClassA() { console.log(this.x, arguments) } ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind({x : "fromBind"}, "bindArg"); ClassB.prototype = {x : "fromProtoB" }; new ClassA("callArg"); new ClassB("callArg"); ClassB("callArg"); ClassB.call({x: "fromCall"}, 'callArg'); 

Answers
fromProtoA ["callArg"]
fromProtoA ["bindArg", "callArg"]
fromBind ["bindArg", "callArg"]
fromBind ["bindArg", "callArg"]

Before you analyze - I will list the main features of bind in accordance with the standard .

2.3.1. Internal properties

Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]] and [[Scope]] internal properties.

This restriction distinguishes the built-in implementation of bind from manually defined methods (for example, a variant from MDN )

2.3.2. call and apply

The behavior of the call and apply methods differs from the standard behavior for functions, namely:
 boundFn.[[Call]] = function (thisValue, extraArgs): var boundArgs = boundFn.[[BoundArgs]], boundThis = boundFn.[[BoundThis]], targetFn = boundFn.[[TargetFunction]], args = boundArgs.concat(extraArgs); return targetFn.[[Call]](boundThis, args); 

The code shows that thisValue is not used anywhere. Thus, it is impossible to change the call context for functions obtained using Function.prototype.bind using call and apply !

2.3.3. In the constructor

In the constructor, this refers to the new (created) object. In other words, the context specified with bind is simply ignored. The constructor calls the normal [[Call]] of the source function .
Important! If there is no return this in the constructor, then the return value is generally undefined and depends on the return value of the new function!

Case study

 function ClassA() { console.log(this.x, arguments) } ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind({x : "fromBind"}, "bindArg"); //   2.3.1,         built-in  Function.prototype.bind //     bind (,   MDN)     ClassB.prototype = {x : "fromProtoB" }; //    -  bind     // : fromProtoA ["callArg"] new ClassA("callArg"); //   2.3.3 - this    .   bind    bindArg,         // : fromProtoA ["bindArg", "callArg"] //    bind   : fromBind ["bindArg", "callArg"]. new ClassB("callArg"); //   bind ,      {x : "fromBind"},   bindArg (  bind),  - "callArg" // : fromBind ["bindArg", "callArg"] ClassB("callArg"); //   2.3.2. ,     call  ,    bind   . // : fromBind ["bindArg", "callArg"] ClassB.call({x: "fromCall"}, 'callArg'); 


Conclusion


In this post, I tried to describe the main methods of binding context to functions, and also described some features in the work of Function.prototype.bind, while I tried to leave only the important details (from my point of view).
If you notice any errors / inaccuracies or want to clarify / add something - write in the LAN, correct

Source: https://habr.com/ru/post/149581/


All Articles