关于javascript:call and apply有什么区别?

What is the difference between call and apply?

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

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

func.apply();func.call();的比较

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


不同的是,apply允许您使用arguments作为数组调用函数;call要求显式列出参数。一个有用的助记符是"a代表数组,c代表逗号"。

请参阅MDN关于Apply和Call的文档。

伪语法:

theFunction.apply(valueForThis, arrayOfArgs)

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

从ES6开始,还有spread阵列与call功能一起使用的可能性,您可以在这里看到兼容性。

样例代码:

1
2
3
4
5
6
7
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


斯科特·艾伦对这件事有一个很好的描述。

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

The apply() method is identical to call(), except apply() requires an array as the second parameter. The array represents the arguments for the target method."

所以:

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


要回答有关何时使用每个函数的部分,如果您不知道将要传递的参数数量,或者如果它们已经在数组或类似数组的对象(如arguments对象)中,请使用apply来转发您自己的参数。否则使用call,因为不需要将参数包装在数组中。

1
2
3
4
5
6
7
8
9
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使用数组,并且始终接受一个或两个参数。使用call时,必须计算参数的数目。


虽然这是一个古老的话题,但我只是想指出,呼叫比.apply快一点。我不能确切地告诉你原因。

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

[UPDATE!号]

道格拉斯·克罗克福德简单地提到了两者之间的区别,这可能有助于解释性能差异…http://youtu.be/ya4uhuxnygm?t=15M52S

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

.apply(this, [...])

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


以下是结尾部分的摘录:迈克尔·博林的权威指南。它可能看起来有点冗长,但它充满了许多洞察力。来自"附录B.经常误解javascript概念":

调用函数时,this指的是什么

当调用foo.bar.baz()形式的函数时,对象foo.bar被称为接收器。当调用函数时,它是用作this值的接收器:

1
2
3
4
5
6
7
8
9
10
11
12
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是全局对象。这会导致一些令人惊讶的行为:

1
2
3
4
5
6
7
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所指的对象)成为可能。方法签名如下:

1
2
3
4
5
6
7
8
9
10
/**
* @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()接收它们作为单个数组:

1
2
3
4
// 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引用相同的函数:

1
2
obj.addValues.call(obj, 10, 20, 30);
obj.addValues.apply(obj, [10, 20, 30]);

但是,由于call()apply()都没有在未指明的情况下使用自己的接收者的值来代替接收者参数,因此以下内容将不起作用:

1
2
3
// 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的属性。

把一个函数看作不知道它被分配到的变量,这可能是有帮助的。这有助于强化这样一种观点,即当调用函数而不是定义函数时,将绑定该函数的值。

提取结束。


有时一个对象借用另一个对象的函数是有用的,这意味着借用对象只是简单地执行lent函数,就好像它是自己的一样。

一个小代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

这些方法对于赋予对象临时功能非常有用。


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

  • bind返回可以执行的函数的实例
  • 第一个参数是"this"
  • 第二个参数是用逗号分隔的参数列表(如调用)
  • }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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
    */

    我想举一个例子,其中使用了"valueforthis"参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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/


    来自函数.prototype.apply()上的MDN文档:

    The apply() method calls a function with a given this value and
    arguments provided as an array (or an array-like object).

    Syntax

    1
    fun.apply(thisArg, [argsArray])

    来自函数.prototype.call()上的MDN文档:

    The call() method calls a function with a given this value and arguments provided individually.

    Syntax

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

    来自javascript中的function.apply和function.call:

    The apply() method is identical to call(), except apply() requires an
    array as the second parameter. The array represents the arguments for
    the target method.

    代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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 += 'output ' + position + '' + JSON.stringify(obj) + '

    '
    ;
    }

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

    另见这把小提琴。


    根本区别在于,call()接受一个参数列表,而apply()接受一个参数数组。


    这是一个很小的帖子,我在上面写道:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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"


    区别在于,call()单独接受函数参数,apply()接受数组中的函数参数。


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

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

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

    使用Apply over调用有一个优点,我们不需要更改参数的数目,只需要更改传递的数组。

    性能差别不大。但是我们可以说调用比apply快一些,因为数组需要在apply方法中进行计算。


    这些到方法的区别在于,您希望如何传递参数。

    "A代表数组,C代表逗号"是一个方便的助记法。


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

    我在apply中看到的优势是,我们可以轻松地将函数调用委托给其他函数,而无需太多努力;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function sayHello() {
      console.log(this, arguments);
    }

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

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

    观察我们用applyhello委托给sayHello有多容易,但用call很难做到。


    虽然callapply实现了相同的目标,但我认为至少有一个地方不能使用call但只能使用apply。这就是您希望支持继承并调用构造函数的时候。

    这里有一个函数允许您创建类,它还支持通过扩展其他类来创建类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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


    主要的区别是,使用call,我们可以更改作用域并正常传递参数,但是apply允许您使用参数作为数组来调用它(将它们作为数组传递)。但就它们在代码中的作用而言,它们非常相似。

    While the syntax of this function is almost identical to that of
    apply(), the fundamental difference is that call() accepts an argument
    list, while apply() accepts a single array of arguments.

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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)

    所以主要的区别就是我们传递参数的方式:呼叫:

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

    适用:

    1
    function.apply(thisArg, [argsArray]);


    总结:

    call()apply()都是位于Function.prototype上的方法。因此,它们通过原型链在每个函数对象上都可用。call()apply()都可以执行一个指定值为this的函数。

    call()apply()的主要区别在于,你必须在其中传递论点。在call()apply()中,作为第一个参数传递希望成为this值的对象。其他参数的不同之处如下:

    • 使用call()时,您必须正常地输入参数(从第二个参数开始)
    • 使用apply()时,必须传递参数数组。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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

    为什么需要使用这些函数?

    在javascript中,this值有时会很复杂。执行函数时确定的this的值,而不是定义函数时确定的值。如果我们的功能依赖于正确的this绑定,我们可以使用call()apply()来强制执行这种行为。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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