What's the meaning to chain call and apply tog

2019-01-23 08:46发布

问题:

I come across this code in jsGarden, and I cannot figure the meaning to chain call and apply together. Both will execute the function with a given context object, why it could be chained?

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// Create an unbound version of "method" 
// It takes the parameters: this, arg1, arg2...argN
Foo.method = function() {

    // Result: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

回答1:

It's making a call to call via apply; that is, it's using call to call a function ("method"), and it's using apply to make the call because it's got the arguments in the form of an (almost) array.

So to take it apart:

Function.call

That's a reference to the call() function available on all Function instances, inherited from the Function prototype.

Function.call.apply

That's a reference, via the reference to the call function, to apply. Because apply is referenced via the call object, when the call to apply is made the this value will be a reference to the call function.

Function.call.apply(Foo.prototype.method, arguments);

So we're invoking the call function via apply, and passing Foo.prototype.method to be the this value, and the arguments to "Foo.mmethod" as the arguments.

I think it's basically the same effect as this:

Foo.method = function() {
  var obj = arguments[0], args = [].slice.call(arguments, 1);
  Foo.prototype.method.apply(obj, args);
}

but I'll have to try it to make sure. edit Yes that seems to be it. So I can summarize the point of that trick as being a way to invoke apply() when the desired this value is the first element of the array holding the parameters. In other words, usually when you call apply() you've got the desired this object reference, and you've got the parameters (in an array). Here, however, since the idea is that you pass in the desired this as a parameter, then it needs to be separated out in order for a call to apply to be made. Personally I would do it as in my "translation" because it's a little less mind-bending (to me), but I suppose one could get used to it. Not a common situation, in my experience.



回答2:

I think the code should be like this:

function Foo() {}

Foo.prototype.method = function(a, b, c) {
 console.log(this, a, b, c);
};

Foo.method = function() {

 //Notice this line:
 Function.apply.call(Foo.prototype.method, this, arguments);
};

then

Foo.method(1,2,3) => function Foo() {} 1 2 3

Other examples:

Function.apply.call(Array,this,[1,2]) => [1, 2]
Function.call.apply(Array,this,[1,2]) => [window]
Function.call.call(Array,this,[1,2])  => [[1, 2]]


回答3:

apply does take an array as the second argument, call takes single parameters.

 // lets take call,
 var callfn = Function.prototype.call;
 // an ordinary function from elsewhere
 var method = Foo.prototype.method;
 // and apply the arguments object on it:
 callfn.apply(method, arguments);

So, the first arguments item will be the this value of the method, and the subsequent will fill the single parameters.

The result is a static function method on the Foo constructor, that takes a Foo instance (or something similiar) as the first argument and applies the prototype method on it. A possible usecase were to define a Object.hasOwnProperty function, which is normally only available as Object.prototype.hasOwnProperty.

Ultimately, it makes the invocation of the method one "prototype" and one "call" shorter if you need to apply it on objects that a) don't inherit it or b) overwrite it.



回答4:

Person.prototype.fullname = function(joiner, options) {
  options = options || { order: "western" };
  var first = options.order === "western" ? this.first : this.last;
  var last =  options.order === "western" ? this.last  : this.first;
  return first + (joiner || " ") + last;
};

// Create an unbound version of "fullname", usable on any object with 'first'
// and 'last' properties passed as the first argument. This wrapper will
// not need to change if fullname changes in number or order of arguments.
Person.fullname = function() {
  // Result: Person.prototype.fullname.call(this, joiner, ..., argN);
  return Function.call.apply(Person.prototype.fullname, arguments);
};

The Code from Javascript Garden.

Notice that it state the

Function.call.apply(Person.prototype.fullname, arguments);
will become this:

Person.prototype.fullname.call(this, joiner, ..., argN);

It means the apply() function will be excuated first, then the call() function will be execuated.

Pattern: The right most call() / apply() will get execuated first

  1. So right most apply() will get executed first
  2. The context of the apply() becomes the caller of the call() function, so now
    Person.prototype,fullname.call()
  3. apply()can only take one single array of parameters, so apply() provides arguments to the call() function, so now
    Person.prototype,fullname.call(arguments)

Examples from @foxiris

1st One:

Function.apply.call(Array,this,[1,2])
  1. The right most call() will get execuated first
  2. The context of call() becomes the caller of the apply() ,so now Array.apply()
  3. The call() can accept multiple arugments, so it can provide this and [1, 2] to apply(), so now Array.apply(this, [1, 2]);, which will output [1, 2]

2nd One:

Function.call.apply(Array,this,[1,2])
  1. The right most apply() will get execuated first
  2. The context of apply() becomes the caller of the call() ,so nowArray.call()
  3. The apply() can only take one single array parameter, so it can only provide this to call(), so now Array.call(this);, output is [].

3rd One:

Function.call.call(Array,this,[1,2])
  1. The right most call() will get execuated first
  2. The context of call()(right most one) becomes the caller of call()(the second to the right), so now Array.call()
  3. The call() can take multiple parameters, so it can provide this and [1, 2] to another call(), so now Array.call(this, [1, 2]);, output is [[1, 2]].