可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
- So right most
apply()
will get executed first
- The context of the
apply()
becomes the caller of the call()
function, so now Person.prototype,fullname.call()
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])
- The right most
call()
will get execuated first
- The context of
call()
becomes the caller of the apply()
,so now Array.apply()
- 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])
- The right most
apply()
will get execuated first
- The context of
apply()
becomes the caller of the call()
,so nowArray.call()
- 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])
- The right most
call()
will get execuated first
- The context of
call()
(right most one) becomes the caller of call()
(the second to the right), so now Array.call()
- 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]]
.