Chaining 'bind' And 'call' In Javascript?
Solution 1:
var g = f.call.bind(f);
. I can't understand this with my first sight.
I assume you're familar with both the .call()
and .bind()
Function
methods? Well, it binds the f.call
method to the function f
.
Notice that f.call
is just Function.prototype.call
, it doesn't matter that we access it as a property on f
because we don't call it here.
So does it has some direct meaning?
It might become more obvious when we look at the ES6 equivalent:
(Function.prototype.call).bind(f) // or
(f.call).bind(f) // is basically
(...args) => f.call(...args) // or more clear
(ctx, ...args) => f.call(ctx, ...args)
Does it have some appropriate usage scenarios?
Well, now that you know what it does, yes. It can take a prototype method and transforms it to a static function that takes the instance as the first argument. An example:
functionExample(n) { this.name = n; }
Example.prototype.display = function() { console.log(this.name); }
Example.display = Function.call.bind(Example.prototype.display);
var e = newExample;
e.display(); // is the same asExample.display(e);
Are there any laws for further chaining
call
/apply
/bind
?
Yes: as always, only the last property in the chain is actually called as a method. In the above example, theres no difference between f.call.bind(…)
, Function.prototype.call.bind(…)
or Function.call.apply.bind.call.bind(…)
- it always calls bind
on call
.
However, by passing them as arguments to each other, you can do somecrazythings which are more or less useful.
Solution 2:
Good question. Let's start off by considering an example that's come up on StackOverflow before: mapping all the strings in an array to lowercase. Of course I can write
strings . map(function(string) { returnstring.toLowerCase(); })
but that seems a bit verbose. I'd rather write
strings . map(CALL_LOWERCASE_WITH_ELT_AS_THIS)
So I might try
strings . map(String.prototype.toLowerCase)
or, to use the shorter idiom some prefer
strings . map(''.toLowerCase)
because ''.toLowerCase
is exactly equal to String.prototype.toLowerCase
.
But this won't work, of course, because map
passes each element to the specified function as its first argument, not as its this
. Therefore, we need somehow to specify a function whose first argument is used to call some other function as its this
. That, of course, is exactly what Function.call
does:
function.call(context)
The first argument to call
("context") is used as the this
when calling function
.
So, problem solved? We ought to be able to just say:
strings . map(''.toLowerCase.call)
and people have tried this and then wonder why it didn't work. The reason is that even though we are passing call
of toLowerCase
as the callback to map
, map
still has no idea that the callback is supposed to be called with a this
of ''.toLowerCase
. We need to explicitly tell map
which this
to use to call the function, which in the case of map
we can do with its second "context" argument:
strings . map(''.toLowerCase.call, ''.toLowerCase)
Actually, since call
is the same on any function object, we can simplify this to just
strings . map(Function.call, ''.toLowerCase)
This works and gets the job done beautifully.
However, whereas map
provides this second "context" argument to specify the this
to call the callback with, that is not something we can depend on being available in all situations. We need a more general way to say "make a function which calls Function.call
with some particular function as this
".
That is exactly what bind
does. It says "take a function and make another function which calls it with a particular this
":
function.bind(context)
In our case, what we want to do is to "take the function Function.call
and make another function which calls it with a this
of ''.toLowerCase
. That is simply
Function.call.bind(''.toLowerCase)
Now we can pass this to map
without having to use the second argument:
strings . map(Function.call.bind(''.toLowerCase))
That works exactly the same as strings . map(Function.call, ''.toLowerCase)
, because in general map(fn, ctxt)
is precisely equal to map(fn.bind(ctxt))
.
The following breaks this down into a readable form, step by step:
Function . //From the Function object
call . // take the `call` method
bind( //and make a newfunction which calls it with a 'this'of''.toLowerCase // `toLowerCase`
)
When this construct is specified as a callback, such as to map
, it means:
Invoke
call
with the first argument passed in and''.toLowerCase
asthis
, which by virtue of the definition ofcall
, means to calltoLowerCase
with that argument asthis
.
Some people prefer to simplify this a bit by saying
var call = Function.call;
var toLowerCase = ''.toLowerCase;
strings . map(call.bind(toLowerCase))
or, using the second argument provided by map
, just
strings . map(call, toLowerCase)
which is almost readable as English: "map each string to the result of calling toLowerCase.
Another common, related use case would be specifying the callback in a then
on a promise. Consider the following code:
promise . then(function(result) { result.frombulate(); })
That's fine, but it's a bit verbose. And then
has no way to pass in a context to be used as this
when invoking the success or failure handler. But with the above, we can now write:
promise . then(call.bind(frombulate))
There are other use cases for the call.bind
idiom, but this is one of the most common ones: define a callback whose effect is to invoke some function with the parameter passed to the callback as its this
.
With ES6 fat arrow functions, of course, I can write
promise . then(result => result.frombulate())
so there is relatively less advantage in the shorthand offered by call.bind(frombulate)
, and it is hard to deny that the fat-arrow version is more readable than that using bind
.
The following question might be of interest too: Array.map and lifted functions in Javascript.
Solution 3:
m.call.bind(m)
can be used as shorthand for:
function(x){return m.bind(x)()}
The former is the "point-free" form of the latter, arguments are implicit. It would be useful with list-operations like map(), making them shorter. You can write stuff like:
let m = "".toUpperCase;
let fun = m.call.bind(m);
let see = ['a','b'].map(fun);
Post a Comment for "Chaining 'bind' And 'call' In Javascript?"