致电与申请之间有什么区别?


3101

使用callapply调用函数有什么区别?

var func = function() {
  alert('hello!');
};

func.apply();func.call();

前述两种方法之间是否存在性能差异?什么时候最好使用callover apply,反之亦然?


726
考虑a在申请args数组和c在args列的调用中。
拉里·

176
@LarryBattle我几乎做同样的事情,但是我认为a在申请数组中使用c在调用逗号(即逗号分隔的参数)中。
萨米,2013年

3
我同意这很愚蠢。令人讨厌的是,在某个采访中以某种方式询问了这个问题,因为一些有影响力的笨蛋将该问题添加到了重要的js问题列表中。
林戈2014年

6
您一次申请一份工作(一个论点),您[打电话] 多次打电话给别人(几个论点)。替代方案:[太多?]有很多《使命召唤》游戏。
Gras Double 2014年

1
如果打算使用参数值列表来调用可变参数函数,而不管其“ this”值如何,请使用ES6扩展运算符,例如fn(...input),其中input是一个数组。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Gajus

Answers:


3644

不同之处在于apply,您arguments可以使用数组作为函数来调用函数。call需要明确列出参数。有用的助记是用于一个 rray和çÇ OMMA”。

有关applycall的信息,请参见MDN的文档。

伪语法:

theFunction.apply(valueForThis, arrayOfArgs)

theFunction.call(valueForThis, arg1, arg2, ...)

从ES6开始,spreadcall函数还可以使用数组,您可以在此处查看兼容性。

样例代码:

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


24
要添加的一件事是,args必须是数字数组([])。关联数组({})将不起作用。
凯文·施罗德

325
@KevinSchroeder:用javascript的话来说,[]叫做数组{}叫做对象
Martijn 2013年

89
我经常忘记使用哪个数组,而希望您列出参数。我曾经记得的一种技术是,如果该方法的第一个字母以a开头,那么它需要一个数组,即 一个
pply

16
@SAM使用呼叫而不是普通函数调用的只有当你需要改变的值才有这样的函数调用。一个示例(将函数arguments对象转换为数组):Array.prototype.slice.call(arguments)[].slice.call(arguments)。如果您在数组中有参数,例如在调用具有(几乎)相同参数的另一个函数的函数中,则apply有意义。建议funcname(arg1)如果需要的话,请使用正常的函数调用,并保存调用并在您确实需要它们的特殊情况下申请
一些

4
@KunalSingh两者callapply都带有两个参数。apply' and call`函数的第一个参数必须是所有者对象,第二个参数分别是数组或逗号分隔的参数。如果您通过nullundefined作为第一个参数,则在非严格模式下将其替换为全局对象,即window
AJ Qarshi

229

斯科特·艾伦(K. Scott Allen)在这件事上写得很好

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

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

所以:

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

42
apply()和call()的第二个参数是可选的,不是必需的。
愤怒的猕猴桃

34
也不需要第一个参数。
Ikrom 2013年

@Ikrom,不是第一个参数,call而是 第一个参数apply
iamcastelli

160

要回答有关何时使用每个函数的部分,apply请在不知道​​要传递的参数数量的情况下,或者如果它们已存在于数组或类似数组的对象(例如arguments用于转发自己的参数的对象)中,请使用。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


111

这是一个很好的助记符。 一个人使用A rays,而一个人则使用一两个参数。当您使用Ç所有你必须ç指望。2-15参数的个数。


2
有用的助记符就在那里!因为不需要第一个或第二个参数,apply所以我将“一个或两个参数”更改为“最多两个参数” 。我不确定为什么会调用applycall没有参数。喜欢一个人看起来正试图为什么在这里找出stackoverflow.com/questions/15903782/...
dantheta

92

虽然这是一个老话题,但我只是想指出.call比.apply快一点。我不能告诉你为什么。

参见jsPerf,http: //jsperf.com/test-call-vs-apply/3


[ UPDATE!]

道格拉斯·克罗克福德(Douglas Crockford)简要提到了两者之间的区别,这可能有助于解释性能差异... http://youtu.be/ya4UHuXNygM?t=15m52s

Apply接受一个参数数组,而Call接受零个或多个单独的参数!啊哈!

.apply(this, [...])

.call(this, param1, param2, param3, param4...)


这取决于函数对参数/数组的处理方式,如果不需要处理数组,是否需要更少的时间?
Eric Hodonsky'3

12
有趣的是,即使没有数组,调用仍然要快得多。jsperf.com/applyvscallvsfn2
乔什·麦克

@JoshMc那将是非常特定于浏览器的。在IE 11中,我的申请速度是通话速度的两倍。
文森特·麦克纳伯

1
1.创建一个新数组意味着垃圾收集器将需要在某个时候清理它。2.使用取消引用访问数组中的项目要比直接访问变量(参数)的效率低。(我相信kmatheny所谓的“解析”实际上是完全不同的东西。)但是我的论点都没有解释jsperf。这必须与引擎的两个函数的实现有关,例如,如果未传递任何函数,则它们可能仍会创建一个空数组。
joeytwiddle13年

感谢您分享测试和视频
Gary

76

摘录自Michael Bolin的《闭幕:权威指南》的摘录。它可能看起来有些冗长,但是它充满了很多见识。摘自“附录B:JavaScript概念经常被误解”:


this调用函数时指的是什么

当调用形式的函数时foo.bar.baz(),该对象foo.bar称为接收器。调用该函数时,将接收方用作以下值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”中所述,window是在Web浏览器中执行JavaScript时的全局对象。这导致一些令人惊讶的行为:

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]);

以下调用是等效的,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不能为nullundefined调用函数时的值。当将nullundefined作为接收者提供给call()或时apply(),全局对象将用作接收者的值。因此,先前的代码具有将命名属性添加value到全局对象的相同的不良副作用。

认为函数不了解为其分配的变量可能会有所帮助。这有助于加强这样的想法,即在调用函数时而不是在定义函数时将绑定此值。


提取结束。


只是要注意一个事实,那additionalValues就是obj.addValues体内没有提及
Viktor Stolbin 2015年

我知道您在回答这个问题,但想补充:定义f时可以使用bind。 var f = obj.addValues;变成var f = obj.addValues.bind(obj) 现在f(20)可以工作,而不必每次都使用调用或应用。
jhliberty

我知道您没有写过,但是您确实强调了本书中的文字和示例,因此,我非常感谢。他们非常有帮助。
猎鹰

34

有时一个对象借用另一个对象的功能很有用,这意味着借用对象只是像执行其自己的操作一样简单地执行了lent函数。

一个小代码示例:

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以及如何使用它?
2014年

25

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

  1. 绑定返回可以执行的函数的实例
  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
*/

23

我想展示一个使用'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 *


20

Call()采用逗号分隔的参数,例如:

.call(scope, arg1, arg2, arg3)

和apply()需要一个参数数组,例如:

.apply(scope, [arg1, arg2, arg3])

以下是一些其他用法示例:http : //blog.i-evaluation.com/2012/08/15/javascript-call-and-apply/


`// call()===以逗号分隔的参数(arguments-list).call(this,args1,args2,args3,...)// apply()===参数数组(array-items)。 apply(this,[arr0,arr1,arr2,...])`
xgqfrms 2016年

19

Function.prototype.apply()的MDN文档中

apply()方法使用给定的函数调用 this值和作为数组(或类似数组的对象)提供的参数。

句法

fun.apply(thisArg, [argsArray])

来自Function.prototype.call()的MDN文档

call()方法使用给定的函数调用 this值和参数的函数。

句法

fun.call(thisArg[, arg1[, arg2[, ...]]])

JavaScript中的Function.apply和Function.call

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


代码示例:

var doSomething = function() {
    var arr = [];
    for(i in arguments) {
        if(typeof this[arguments[i]] !== 'undefined') {
            arr.push(this[arguments[i]]);
        }
    }
    return arr;
}

var output = function(position, obj) {
    document.body.innerHTML += '<h3>output ' + position + '</h3>' + JSON.stringify(obj) + '\n<br>\n<br><hr>';
}

output(1, doSomething(
    'one',
    'two',
    'two',
    'one'
));

output(2, doSomething.apply({one : 'Steven', two : 'Jane'}, [
    'one',
    'two',
    'two',
    'one'
]));

output(3, doSomething.call({one : 'Steven', two : 'Jane'},
    'one',
    'two',
    'two',
    'one'
));

另请参见此Fiddle



10

这是我写的一篇小文章:

http://sizeableidea.com/call-versus-apply-javascript/

var obj1 = { which : "obj1" },
obj2 = { which : "obj2" };

function execute(arg1, arg2){
    console.log(this.which, arg1, arg2);
}

//using call
execute.call(obj1, "dan", "stanhope");
//output: obj1 dan stanhope

//using apply
execute.apply(obj2, ["dan", "stanhope"]);
//output: obj2 dan stanhope

//using old school
execute("dan", "stanhope");
//output: undefined "dan" "stanhope"

这是另一个:blog.i-evaluation.com/2012/08/15/javascript-call-and-apply 但基本上是正确的:.call(scope,arg1,arg2,arg3)
Mark

7

区别在于,分别call()采用函数参数,并apply()采用数组中的函数参数。


6

我们可以区分调用和应用方法,如下所示

CALL:带有参数的函数单独提供。如果您知道要传递的参数,或者没有要传递的参数,则可以使用call。

APPLY:调用带有以数组形式提供的参数的函数。如果您不知道要传递给函数多少参数,则可以使用apply。

使用apply over调用有一个优势,我们只需要更改传递的数组,就不需要更改参数数量。

性能没有太大差异。但是我们可以说调用与应用相比要快一些,因为数组需要在apply方法中求值。


5

这些方法之间的区别在于,您要如何传递参数。

“ A用于数组,C用于逗号”是一个方便的助记符。


11
此答案提供了其他答案中尚未提供的内容?
凯尔(Kyll)2015年

5

调用和应用都用于在this执行函数时强制使用该值。唯一的区别是,call需要n+1的参数,其中1 this'n' argumentsapply只接受两个参数,一个this是参数数组。

优点我看到applycall的是,我们可以轻松地委派一个函数调用来毫不费力等功能;

function sayHello() {
  console.log(this, arguments);
}

function hello() {
  sayHello.apply(this, arguments);
}

var obj = {name: 'my name'}
hello.call(obj, 'some', 'arguments');

观察我们如何轻松地委派hellosayHello使用apply,但是call这是很难实现的。


4

即使callapplyachive同样的事情,我觉得有ATLEAST一个地方,你不能使用call,但只能使用apply。那就是您想要支持继承并想要调用构造函数的时候。

这是一个允许您创建类的函数,该函数还支持通过扩展其他类来创建类。

function makeClass( properties ) {
    var ctor = properties['constructor'] || function(){}
    var Super = properties['extends'];
    var Class = function () {
                 // Here 'call' cannot work, only 'apply' can!!!
                 if(Super)
                    Super.apply(this,arguments);  
                 ctor.apply(this,arguments);
                }
     if(Super){
        Class.prototype = Object.create( Super.prototype );
        Class.prototype.constructor = Class;
     }
     Object.keys(properties).forEach( function(prop) {
           if(prop!=='constructor' && prop!=='extends')
            Class.prototype[prop] = properties[prop];
     });
   return Class; 
}

//Usage
var Car = makeClass({
             constructor: function(name){
                         this.name=name;
                        },
             yourName: function() {
                     return this.name;
                   }
          });
//We have a Car class now
 var carInstance=new Car('Fiat');
carInstance.youName();// ReturnsFiat

var SuperCar = makeClass({
               constructor: function(ignore,power){
                     this.power=power;
                  },
               extends:Car,
               yourPower: function() {
                    return this.power;
                  }
              });
//We have a SuperCar class now, which is subclass of Car
var superCar=new SuperCar('BMW xy',2.6);
superCar.yourName();//Returns BMW xy
superCar.yourPower();// Returns 2.6

我相信呼叫将与所选答案中所述的价差操作员一起工作。除非我错过了什么。
jhliberty

4

摘要:

双方call()apply()都分别位于方法Function.prototype。因此,它们可通过原型链在每个功能对象上使用。二者call()apply()可以用的规定值执行功能this

call()和之间的主要区别apply()是必须将参数传递给它的方式。在call()和中,apply()您都将作为值的对象作为第一个参数传递this。其他参数在以下方面有所不同:

  • 随着call()你必须把在参数正常(从第二个参数开始)
  • apply()您必须传递参数数组。

例:

let obj = {
  val1: 5,
  val2: 10
}

const summation = function (val3, val4) {
  return  this.val1 + this.val2 + val3 + val4;
}

console.log(summation.apply(obj, [2 ,3]));
// first we assign we value of this in the first arg
// with apply we have to pass in an array


console.log(summation.call(obj, 2, 3));
// with call we can pass in each arg individually

为什么我需要使用这些功能?

this有时在javascript中,该值可能很棘手。在不定义功能时执行功能时this确定的值如果我们的功能依赖于正确的this绑定,则可以使用call()apply()强制执行此行为。例如:

var name = 'unwantedGlobalName';

const obj =  {
  name: 'Willem',
  sayName () { console.log(this.name);}
}


let copiedMethod = obj.sayName;
// we store the function in the copiedmethod variable



copiedMethod();
// this is now window, unwantedGlobalName gets logged

copiedMethod.call(obj);
// we enforce this to be obj, Willem gets logged


4

主要区别在于,使用call可以正常更改范围并传递参数,但是apply可以将参数用作Array来调用它(将它们作为数组传递)。但是就它们在代码中要做的事情而言,它们非常相似。

尽管此函数的语法与apply()的语法几乎相同,但基本区别在于call()接受参数列表,而apply()接受单个参数数组。

因此,正如您所看到的,并没有太大的区别,但是在某些情况下,我们更喜欢使用call()或apply()。例如,看下面的代码,使用apply方法从MDN查找数组中最小和最大的数字:

// min/max number in an array
var numbers = [5, 6, 2, 3, 7];

// using Math.min/Math.max apply
var max = Math.max.apply(null, numbers); 
// This about equal to Math.max(numbers[0], ...)
// or Math.max(5, 6, ...)

var min = Math.min.apply(null, numbers)

因此,主要区别只是传递参数的方式:

调用:

function.call(thisArg, arg1, arg2, ...);

应用:

function.apply(thisArg, [argsArray]);

2

让我为此添加一些细节。

这两个调用几乎是等效的:

func.call(context, ...args); // pass an array as list with spread operator

func.apply(context, args);   // is same as using apply

只有一个小差异:

  • spread运营商...允许通过迭代 args的列表来调用。
  • apply仅接受阵列状 ARGS。

因此,这些调用相互补充。我们期望的迭代call作品,我们期待一个类似数组的apply作品。

对于既可迭代类似于数组的对象(如真实数组),从技术上讲,我们可以使用它们中的任何一个,但是应用可能会更快,因为大多数JavaScript引擎在内部对其进行了优化。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.