简体   繁体   中英

JavaScript: The Good Parts - chapter 8, function.apply()

For the following code, based on page 84 of "JavaScript: The Good Parts", can someone explain why [1] and [0] are used? I understand that they're supplying the arguments 1 and 0 to slice, respectively, but what's the point of that?

Function.prototype.bind = function (that) { 
  var method = this;
  var slice = Array.prototype.slice;
  var args = slice.apply(arguments, [1]); // Why is [1] here?

  return function () { 
    return method.apply(that, args.concat(slice.apply(arguments, [0]))); // Why is [0] here?
  };
};

var x = function () { 
  return this.value;
}.bind({ value: 666 }); 

console.log(x()); // Returns 666.

I believe I understand the big picture - the function x has been designed to extract the value of the value property. We then bind an object that has a value property name/value pair and execute the x function. The x function reaches into the supplied object, as if it where a method of that object, and returns the value of the value property.

What I do not understand is how this is accomplished (I note Crockford's quasi-amusing use of 666 ). Thanks in advance.

The second argument for apply needs to be an array, if [2] were 2 instead, this would happen

Array.prototype.slice.apply(arguments, 2); Uncaught TypeError: Function.prototype.apply: Arguments list has wrong type

arguments is a baked in property which represents all of the arguments send to a function in an array like object. slicing this array will remove unwanted parts. In the case of bind, that is passed as an argument so slicing after 1 will remove that.

As a result

var args = slice.apply(arguments, [1]); // Why is [1] here?

Will take all of the extra arguments sent to bind, for example .bind({},1,2,3,4) will result in args being [1,2,3,4]

Next, a function is returned

return function () { 
 return method.apply(that, args.concat(slice.apply(arguments, [0]))); // Why is [0] here?
};

that is going to be the new scope to use, method is the function that bind was originally called from, args.concat is going to take the previous array examined, and then add in all of the arguments which the method was called with, which is why [0] was used as opposed to [1] (where that was passed and was not being used as an argument for the method).

Function.prototype.apply() takes 2 arguments, a value for this and an array of arguments . Even if there is only one argument, it expects an array.

So:

slice.apply(arguments, [1]);

is almost equivalent to:

arguments.slice(1);

I say almost because arguments is not a real array, and it does not have a slice method, which is why you have to do this the funky way.

As others note in the comments, you can use call instead to make this cleaner. call expects a value for this this and then any number of additional individual arguments. Which means you can instead do:

slice.call(arguments, 1);

Let's assume you have some function, it doesn't matter what it does, and you are binding it to a context and passing some arguments in.

function myFunc(a, b, c) {

}.bind(myContext, arg1, arg2)

Now let's look at what .bind does:

Function.prototype.bind = function (that) { 
  var method = this;
  var slice = Array.prototype.slice;
  var args = slice.apply(arguments, [1]); // Why is [1] here?

apply takes the arguments context, [arg1, arg2, ...] . So slice.apply(arguments, [1]) is like calling arguments.slice(1) . (As noted in the comments, you could use call and drop the array brackets.) The purpose of the 1 is to start the slice from the 1 index. that which is passed in is arguments[0] . Any arguments passed to .bind after the context would be included in this slice. So you might call myFunc.bind(myContext, arg1, arg2) ; that slice would set args to [arg1, arg2] .

  return function () { 
    return method.apply(that, args.concat(slice.apply(arguments, [0]))); // Why is [0] here?
  };
};

This is the function that gets returned by .bind ; this is the function that will actually be called when myFunc(some, params, here) is called directly. The slice in this case is the entire arguments list; slicing from 0 returns the whole list, converted to an array, so that it can be passed to the outer apply.


TL;DR

The first arguments are sliced from 1 to drop that and get all the arguments after it. The second list is sliced from 0 to include ALL the arguments passed in.

Documentation and Context

Take a closer look at Function.prototype.apply() , the arguments object , and the slice function .

apply takes an object as the context to call a function, with an additional optional parameter of an array of arguments to send to the called function.

Slice(0)

slice(0) is often used to covert array-like objects into arrays.

slice.apply(arguments, [0]) is nearly equivalent to arguments.slice(0), except that it will not alter the arguments sent to the main function, but will result in a new array, guaranteed to be an array.

Slice(1)

Similar to above, but will remove the first entry from the array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM