使用Function.prototype.apply()
和Function.prototype.call()
来调用函数有什么区别?
const func = function() {
alert("Hello world!");
};
func.apply()
与func.call()
这两种方法之间是否存在性能差异?何时最好使用call
而不是apply
,反之亦然?
使用Function.prototype.apply()
和Function.prototype.call()
来调用函数有什么区别?
const func = function() {
alert("Hello world!");
};
func.apply()
与func.call()
这两种方法之间是否存在性能差异?何时最好使用call
而不是apply
,反之亦然?
apply
函数可以使用数组形式的 arguments
调用函数;而 call
函数需要将参数显式列出。一个很有用的记忆口诀是 "A for array and C for comma.",具体可参考 MDN 上关于 apply 和 call 的文档。theFunction.apply(valueForThis, arrayOfArgs)
theFunction.call(valueForThis, arg1, arg2, ...)
从 ES6 开始还可以使用 spread
操作符来将数组展开以便与 call
函数一起使用,对应的兼容性列表可以在 这里 查看。function theFunction(name, profession) {
console.log("My name is " + name + " and I am a " + profession +".");
}
theFunction("John", "fireman");
theFunction.apply(undefined, ["Susan", "school teacher"]);
theFunction.call(undefined, "Claude", "mathematician");
theFunction.call(undefined, ...["Matthew", "physicist"]); // used with the spread operator
[]
被称为数组,{}
被称为对象。 - MartijnArray.prototype.slice.call(arguments)
或[].slice.call(arguments)
。如果您有一个包含参数的数组,则应使用_apply_,例如在调用另一个具有(几乎)相同参数的函数的函数中。建议如果普通函数调用funcname(arg1)
可以满足您的需求,请使用普通函数调用,而将_call_和_apply_保留给那些确实需要它们的特殊情况。 - somecall
和apply
都需要两个参数。apply
和call
函数的第一个参数必须是所有者对象,第二个参数分别为数组或逗号分隔的参数。如果您将null
或undefined
作为第一个参数传递,则在非严格模式下,它们将被替换为全局对象,即window
。 - A J QarshiK. Scott Allen写了一篇不错的文章讨论了这个问题。
基本上,它们在处理函数参数方面有所不同。
apply()方法与call()方法相同, 只是apply()方法需要数组作为第二个参数, 该数组代表目标方法的参数。
所以:
// assuming you have f
function f(message) { ... }
f.call(receiver, "test");
f.apply(receiver, ["test"]);
call
方法的第一个参数是可选的,但对于 apply
方法来说是必需的。 - iamcastelli关于何时使用每个函数的部分,如果你不知道要传递的参数数量或者它们已经在一个数组或类似数组对象中(例如 arguments
对象)以转发自己的参数,那么使用 apply
。否则,使用 call
,因为没有必要将参数包装在一个数组中。
f.call(thisObject, a, b, c); // Fixed number of arguments
f.apply(thisObject, arguments); // Forward this function's arguments
var args = [];
while (...) {
args.push(some_value());
}
f.apply(thisObject, args); // Unknown number of arguments
如果我没有传递任何参数(就像您的示例一样),我更喜欢使用call
,因为我正在调用该函数。而apply
则意味着您正在应用该函数于(不存在的)参数。
除非您使用apply
并将参数包装在数组中(例如f.apply(thisObject, [a, b, c])
而不是f.call(thisObject, a, b, c)
),否则不应该有任何性能差异。我没有测试过,所以可能会有差异,但这取决于浏览器。如果您没有将参数放入数组中,则call
很可能更快,而如果您这样做了,则apply
可能更快。
下面是一个好的助记口诀:Apply 使用数组(Arrays),并且总是带有一个或两个参数。当使用 Call 时,你需要Count 计算参数的数量。
apply
的第一个和第二个参数都不是必需的。但我不确定为什么会在没有参数的情况下调用apply
或call
。看起来有人正在这里尝试找出原因:https://dev59.com/yXDYa4cB1Zd3GeqPDbd0 - dantheta以下是来自《JavaScript权威指南》(第6版)Michael Bolin著的一段摘录。看起来有些冗长,但是包含了大量深入的见解。来自于“附录B. 经常被误解的JavaScript概念”:
this
的指向当调用形如foo.bar.baz()
的函数时,对象foo.bar
被称为接收者(receiver)。在函数被调用时,接收者将作为this
的值:
var obj = {};
obj.value = 10;
/** @param {...number} additionalValues */
obj.addValues = function(additionalValues) {
for (var i = 0; i < arguments.length; i++) {
this.value += arguments[i];
}
return this.value;
};
// Evaluates to 30 because obj is used as the value for 'this' when
// obj.addValues() is called, so obj.value becomes 10 + 20.
obj.addValues(20);
如果在调用函数时没有明确的接收者,那么全局对象就成为接收者。如第47页的“goog.global”所解释的那样,在Web浏览器中执行JavaScript时,window是全局对象。这会导致一些令人惊讶的行为:
var f = obj.addValues;
// Evaluates to NaN because window is used as the value for 'this' when
// f() is called. Because and window.value is undefined, adding a number to
// it results in NaN.
f(20);
// This also has the unintentional side effect of adding a value to window:
alert(window.value); // Alerts NaN
即使obj.addValues
和f
引用同一个函数,但由于每次调用时接收者的值不同,它们的行为不同。因此,当调用引用this
的函数时,重要的是要确保在调用时this
将具有正确的值。需要明确的是,如果函数体中未引用this
,则f(20)
和obj.addValues(20)
的行为将相同。
由于JavaScript中的函数是一等对象,它们可以拥有自己的方法。所有函数都有方法call()
和apply()
,这使得在调用函数时重新定义接收者(即this
所引用的对象)成为可能。方法签名如下:
/**
* @param {*=} receiver to substitute for 'this'
* @param {...} parameters to use as arguments to the function
*/
Function.prototype.call;
/**
* @param {*=} receiver to substitute for 'this'
* @param {Array} parameters to use as arguments to the function
*/
Function.prototype.apply;
请注意call()
和apply()
之间的唯一区别在于,call()
将函数参数作为单独的参数接收,而apply()
将它们作为一个数组接收:
// When f is called with obj as its receiver, it behaves the same as calling
// obj.addValues(). Both of the following increase obj.value by 60:
f.call(obj, 10, 20, 30);
f.apply(obj, [10, 20, 30]);
以下调用是等价的,因为 f
和 obj.addValues
引用了同一个函数:obj.addValues.call(obj, 10, 20, 30);
obj.addValues.apply(obj, [10, 20, 30]);
然而,由于call()
和apply()
在未指定接收器参数时都不使用其自身接收器的值进行替换,因此以下代码将无法正常工作:
// Both statements evaluate to NaN
obj.addValues.call(undefined, 10, 20, 30);
obj.addValues.apply(undefined, [10, 20, 30]);
当函数被调用时,this
的值永远不能为null
或undefined
。当null
或undefined
被作为接收器(receiver)传递到call()
或apply()
中时,全局对象会被使用作为接收器的值。因此,先前的代码具有相同的副作用,即向全局对象添加一个名为value
的属性。additionalValues
在 obj.addValues
函数体内没有被引用。 - Viktor Stolbinvar f = obj.addValues;
变成
var f = obj.addValues.bind(obj)
现在,每次调用f(20)都不需要使用call或apply。 - jhliberty有时候一个对象借用另一个对象的功能很有用,这意味着借用对象仅仅执行借出的函数,就好像它是自己的一样。
一个小代码示例:
var friend = {
car: false,
lendCar: function ( canLend ){
this.car = canLend;
}
};
var me = {
car: false,
gotCar: function(){
return this.car === true;
}
};
console.log(me.gotCar()); // false
friend.lendCar.call(me, true);
console.log(me.gotCar()); // true
friend.lendCar.apply(me, [false]);
console.log(me.gotCar()); // false
这些方法非常有用,可以为对象提供临时的功能。
使用Call,Apply和Bind的另一个示例。 Call和Apply之间的区别是明显的,但Bind的工作方式如下:
}
function Person(name) {
this.name = name;
}
Person.prototype.getName = function(a,b) {
return this.name + " " + a + " " + b;
}
var reader = new Person('John Smith');
reader.getName = function() {
// Apply and Call executes the function and returns value
// Also notice the different ways of extracting 'getName' prototype
var baseName = Object.getPrototypeOf(this).getName.apply(this,["is a", "boy"]);
console.log("Apply: " + baseName);
var baseName = Object.getPrototypeOf(reader).getName.call(this, "is a", "boy");
console.log("Call: " + baseName);
// Bind returns function which can be invoked
var baseName = Person.prototype.getName.bind(this, "is a", "boy");
console.log("Bind: " + baseName());
}
reader.getName();
/* Output
Apply: John Smith is a boy
Call: John Smith is a boy
Bind: John Smith is a boy
*/
Array.prototype.push = function(element) {
/*
Native code*, that uses 'this'
this.put(element);
*/
}
var array = [];
array.push(1);
array.push.apply(array,[2,3]);
Array.prototype.push.apply(array,[4,5]);
array.push.call(array,6,7);
Array.prototype.push.call(array,8,9);
//[1, 2, 3, 4, 5, 6, 7, 8, 9]
**详情请参考: http://es5.github.io/#x15.4.4.7*
Call()函数接收以逗号分隔的参数,例如:
.call(scope, arg1, arg2, arg3)
而apply()函数接收一个参数数组,例如:
.apply(scope, [arg1, arg2, arg3])
以下是一些使用示例: http://blog.i-evaluation.com/2012/08/15/javascript-call-and-apply/
a
,以及在调用列参数时使用的c
。 - Larry Battleargs
,唯一的区别就是三个点...
。例如:fn.apply(context, args)
或者fn.call(context, ...args)
。 - Ankit Singh