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) { return string.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 new function 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
as this
, which by virtue of the definition of call
, means to call toLowerCase
with that argument as this
.
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.
g
with some arguments and looked how they are accessible inf
? – Catena