Jaffer
Jaffer

Reputation: 2968

Understanding Function.call.bind - step-by-step

Everything started with this Question

Then an answer from @MinusFour

var slice = Function.call.bind(Array.prototype.slice);

I wanted to understand, whats happening under the hood, my curiosity hence this Question.

what to achieve ? understanding of "Function.call.bind".

Step-by-step approach for the same

Started with MDN

NOTE : I am using NodeJS here

1)

var adder = new Function('a', 'b', 'return a + b');
console.log(adder(2, 6));

**OUTPUT **

8

This is expected, Nothing Fancy

2)

This is our end goal , calling function myFunc from the bounded function (Function.call.bind(myFunc))

function myFunc(a, b) {
    console.log(arguments.length, a, b, a + b);
}

3)

var adder = Function(myFunc);
console.log(adder.toString())

OUTPUT

function anonymous() { function myFunc(a, b) {   console.log(a + b); } }

Expected! above code does nothing, because i am calling 'anonymous' , and it does nothing.

4)

var adder = Function.call(myFunc);
console.log(adder.toString())

OUTPUT

function anonymous() {

}

Expected!. '.call' calls 'Function', with 'this' set to 'myFunc' and with out any param or function body. so an empty anonymous function is the output. Now, I can do "var adder = Function.call(myFunc,myFunc);" to create the same function from step-3

So far so good

5)

var adder = Function.call.bind(myFunc);
console.log(adder.toString())   
adder(2,6); 

OUTPUT

function () { [native code] }
1 6 undefined NaN

Here first param is not passed to the 'myFunc' function. this is taken as 'this' for function 'adder' (the bounded Function.call) ?

Now I understand(or did I misunderstood?) until now, but then How does below code works ?

var slice = Function.call.bind(Array.prototype.slice);
function fn(){
    var arr = slice(arguments);
}

in my case first param to adder is discarded(or Function.call consider it as 'this' for it), same should happen with slice above right ?

Anyway, i wanted to document it for a reference

Upvotes: 1

Views: 278

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074335

I'm afraid you've gone off in slightly the wrong direction. This line:

var slice = Function.call.bind(Array.prototype.slice);

never calls Function and never arranges for it to be called later. The only thing Function is being used for there is its call property. Function could have been Object or Date or RegExp or any other function, or could have been Function.prototype; doesn't matter. Function.prototype would have been more direct and possibly less confusing.

This is a bit tricky to explain because it involves two layers of dealing with this, where this is different things at different times:

The call function calls functions with a specific this value you give it as its first argument, passing along any other arguments you give it. For example:

function foo(arg) {
    console.log("this.name = " + this.name + ", arg = " + arg);
}
var obj = {name: "bar"};
foo.call(obj, "glarb"); // "this.name = bar, arg = glarb"

There, because we called call on foo, call called foo with this set to obj and passing along the "glarb" argment.

call knows what function it should call based on what this is during the call call. foo.call sets this during call to foo. That can be confusing, so let's diagram it:

  • foo.call(obj, "glarb") calls call:
    • call sees this = foo and the arguments obj and "glarb"
    • call calls this (which is foo):
      • foo sees this = obj and the single argument "glarb"

With regard to slice, you normally see call used with it used to create an array from something array-like that isn't really an array:

var divArray = Array.prototype.slice.call(document.querySelectorAll("div"));

or

var divArray = [].slice.call(document.querySelectorAll("div"));

There, we call call with this set to Array.prototype.slice (or [].slice, which is the same function) and passing in the collection returned by querySelectorAll as the first argument. call calls the function it sees as this, using its first argument as this for that call, and passing along any others.

So that's the first layer of this stuff.

bind is another function that functions have, and it's similar to call but different: Where call calls the target function with a given this and arguments, bind creates and returns a new function that will do that if you call it. Going back to our foo example:

function foo(arg) {
    console.log("this.name = " + this.name + ", arg = " + arg);
}
var obj = {name: "bar"};
var fooWithObjAndGlarb = foo.bind(obj, "glarb");
fooWithObjAndGlarb(); // "this.name = bar, arg = glarb"

This is called binding things (obj and the "glarb" argument) to foo.

Unlike call, since bind creates a new function, we can add arguments later:

function foo(arg) {
    console.log("this.name = " + this.name + ", arg = " + arg);
}
var obj = {name: "bar"};
var fooWithObj = foo.bind(obj);
fooWithObj("glarb"); // "this.name = bar, arg = glarb"

Okay, now we have all our working pieces. So what's happening in your code? Let's break it into parts:

// Get a reference to the `call` function from the `call` property
// on `Function`. The reason `Function` has a `call` property is that
// `Function` is, itself, a function, which means its prototype is
// `Function.prototype`, which has `call` on it.
var call = Function.call;

// Get a reference to the `slice` function from `Array.prototype`'s `slice` property:
var rawSlice = Array.prototype.slice;

// Create a *bound* copy of `call` that, when called, will call
// `call` with `this` set to `rawSlice`
var callBoundToSlice = call.bind(rawSlice);

(callBoundToSlice is just called slice in your question, but I'm using callBoundToSlice to avoid confusion.) Binding rawSlice to call was the first layer of this handling, determining what call will see as this. Calling callBoundToSlice will call call with this set to rawSlice. Then call will call the function it sees as this (rawSlice), using its first argument as the value for this during the call (the second layer of this handling) and passing on any further arguments.

So our forEach with a collection from querySelectorAll can now look like this:

callBoundToSlice(document.querySelectorAll("div")).forEach(function(div) {
    // Each div here
});

That passes the collecton returned by querySelectorAll into callBoundToSlice, which calls call with this as rawSlice, which calls Array.prototype.slice with this set to the collection. Array.prototype.slice uses this to copy the array.


All of that said, using slice to turn array-like objects into true arrays is a bit out of date. ES2015 introduces the Array.from method, which can be shimmed/polyfilled on JavaScript engines that don't have it yet:

var divArray = Array.from(document.querySelectorAll("div"));

Upvotes: 3

Related Questions