调用和应用有什么区别?

3469

使用Function.prototype.apply()Function.prototype.call()来调用函数有什么区别?

const func = function() {
    alert("Hello world!");
};

func.apply()func.call()

这两种方法之间是否存在性能差异?何时最好使用call而不是apply,反之亦然?


778
考虑到在传递参数数组时使用的 a,以及在调用列参数时使用的 c - Larry Battle
205
我会尽力为您翻译。以下是翻译的结果:@LarryBattle 我做的几乎一样,但我认为 apply 中的 a 代表数组,而 call 中的 c 代表逗号(即逗号分隔的参数)。 - Samih
10
我同意这很愚蠢。让人烦恼的是,由于某些有影响力的笨蛋将这个问题添加到他们认为重要的JavaScript问题列表中,所以在面试中仍会问到这个问题。 - Ringo
13
申请工作只需一次(一个参数),而打电话需要多次(几个参数)。另一个例子是有太多《使命召唤》游戏。 - Gras Double
2
在ES6中,如果你有一个参数数组args,唯一的区别就是三个点...。例如:fn.apply(context, args)或者fn.call(context, ...args) - Ankit Singh
显示剩余4条评论
24个回答

4003
apply 函数可以使用数组形式的 arguments 调用函数;而 call 函数需要将参数显式列出。一个很有用的记忆口诀是 "A for array and C for comma.",具体可参考 MDN 上关于 applycall 的文档。
伪代码语法: 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


33
需要补充的一点是,参数必须是数值类型的数组([]),关联数组({})将不起作用。 - Kevin Schroeder
393
在JavaScript中,[]被称为数组{}被称为对象 - Martijn
97
我经常会忘记哪个方法需要传递数组,哪个方法需要列出参数。我用来记忆的技巧是如果方法名称的第一个字母是 a ,那么它需要传递一个数组,即 a pply array。 - aziz punjani
20
使用_call_而不是普通函数调用,只有在需要更改函数调用中_this_的值时才有意义。例如(将函数参数对象转换为数组的示例):Array.prototype.slice.call(arguments)[].slice.call(arguments)。如果您有一个包含参数的数组,则应使用_apply_,例如在调用另一个具有(几乎)相同参数的函数的函数中。建议如果普通函数调用funcname(arg1)可以满足您的需求,请使用普通函数调用,而将_call_和_apply_保留给那些确实需要它们的特殊情况。 - some
6
@KunalSingh callapply都需要两个参数。applycall函数的第一个参数必须是所有者对象,第二个参数分别为数组或逗号分隔的参数。如果您将nullundefined作为第一个参数传递,则在非严格模式下,它们将被替换为全局对象,即window - A J Qarshi
显示剩余7条评论

257

K. Scott Allen写了一篇不错的文章讨论了这个问题。

基本上,它们在处理函数参数方面有所不同。

apply()方法与call()方法相同, 只是apply()方法需要数组作为第二个参数, 该数组代表目标方法的参数。

所以:

// assuming you have f
function f(message) { ... }
f.call(receiver, "test");
f.apply(receiver, ["test"]);

48
apply() 和 call() 的第二个参数是可选的,不是必需的。 - angry kiwi
40
第一个参数也不是必需的。 - Ikrom
3
@Ikrom,call 方法的第一个参数是可选的,但对于 apply 方法来说是必需的。 - iamcastelli

172

关于何时使用每个函数的部分,如果你不知道要传递的参数数量或者它们已经在一个数组或类似数组对象中(例如 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可能更快。


126

下面是一个好的助记口诀:Apply 使用数组(Arrays),并且总是带有一个或两个参数。当使用 Call 时,你需要Count 计算参数的数量。


2
有一个有用的助记符!我将把“一个或两个参数”改为“最多两个参数”,因为apply的第一个和第二个参数都不是必需的。但我不确定为什么会在没有参数的情况下调用applycall。看起来有人正在这里尝试找出原因:https://dev59.com/yXDYa4cB1Zd3GeqPDbd0 - dantheta

98
虽然这是一个老话题,但我想指出,.call比.apply略快。我无法告诉你为什么。
请参见jsPerf:http://jsperf.com/test-call-vs-apply/3
[更新!]
道格拉斯·克罗克福德(Douglas Crockford)简要提到了两者之间的差异,这可能有助于解释性能差异...http://youtu.be/ya4UHuXNygM?t=15m52s apply接受一个参数数组,而call接受零个或多个单独的参数! 啊哈!
.apply(this, [...])
.call(this, param1, param2, param3, param4...)

这取决于函数对参数/数组执行的操作,如果它不需要处理数组,那么它会花费更少的时间吗? - Eric Hodonsky
12
有趣的是,即使没有数组,调用仍然比应用要快得多。http://jsperf.com/applyvscallvsfn2 - Josh Mc
@JoshMc 这将是非常浏览器特定的。在IE 11中,我发现apply比call快两倍。 - Vincent McNabb
1
  1. 创建一个新数组意味着垃圾收集器需要在某个时候清理它。
  2. 使用解引用访问数组中的项比直接访问变量(参数)效率低。(我认为这就是kmatheny所说的“解析”,实际上这是完全不同的事情。)但是我的两个论点都无法解释jsperf。那一定与引擎对这两个函数的实现有关,例如,如果没有传递任何内容,它们可能仍然会创建一个空数组。
- joeytwiddle
1
感谢您分享测试和视频。 - Gary

80

以下是来自《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.addValuesf引用同一个函数,但由于每次调用时接收者的值不同,它们的行为不同。因此,当调用引用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]);
以下调用是等价的,因为 fobj.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的值永远不能为nullundefined。当nullundefined被作为接收器(receiver)传递到call()apply()中时,全局对象会被使用作为接收器的值。因此,先前的代码具有相同的副作用,即向全局对象添加一个名为value的属性。
将函数视为不知道其被分配给哪个变量可能有助于强化这样一个想法:this的值将在函数被调用时绑定,而不是在定义时绑定。
摘自文献结束。

只是要注意一下,additionalValuesobj.addValues 函数体内没有被引用。 - Viktor Stolbin
我知道你在回答问题,但是我想补充一下:在定义f时,你可以使用bind。var f = obj.addValues; 变成 var f = obj.addValues.bind(obj)现在,每次调用f(20)都不需要使用call或apply。 - jhliberty
1
我知道这不是你写的,但你确实将书中的文本和示例标记为相关内容,我非常感激。它们非常有帮助。 - Fralcon

37

有时候一个对象借用另一个对象的功能很有用,这意味着借用对象仅仅执行借出的函数,就好像它是自己的一样。

一个小代码示例:

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

这些方法非常有用,可以为对象提供临时的功能。


1
想要知道如何查看 console.log 的人可以参考:什么是 console.log,我该如何使用它? - Michel Ayres

27

使用Call,Apply和Bind的另一个示例。 Call和Apply之间的区别是明显的,但Bind的工作方式如下:

  1. Bind返回一个可执行函数的实例
  2. 第一个参数是“this”
  3. 第二个参数是逗号分隔的参数列表(类似于Call)

}

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
*/

24
我想展示一个例子,其中使用了“valueForThis”参数:
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*


24

` // call() === comma-separated arguments (arguments-list) .call(this, args1, args2, args3, ...)// apply() === array of arguments (array-items) .apply(this, [arr0, arr1, arr2, ...]) ` - xgqfrms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接