Chaining 'bind' and 'call' in JavaScript?
Asked Answered
M

3

9

When I reading this answer, find var g = f.call.bind(f);. I can't understand this with my first sight.

So does it has some direct meaning, and has some appropriate usage scenarios?

And further when you using call(or apply) or bind or both in chaining, what will happen? Is there some laws?

Molal answered 3/7, 2015 at 4:30 Comment(2)
Have you tried calling g with some arguments and looked how they are accessible in f?Catena
Have a look at this.Catena
C
4

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:

function Example(n) { this.name = n; }
Example.prototype.display = function() { console.log(this.name); }

Example.display = Function.call.bind(Example.prototype.display);

var e = new Example;
e.display(); // is the same as
Example.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 some crazy things which are more or less useful.

Catena answered 3/7, 2015 at 8:3 Comment(0)
R
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) { 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.

Roadrunner answered 3/7, 2015 at 10:13 Comment(0)
C
-1
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);
Caffeine answered 7/9, 2019 at 0:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.