使用Function.pr原型.apply()和Function.pr原型.call()调用函数有什么区别?

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

函数.apply();vs func.call();

上述两种方法之间是否存在性能差异?何时最好使用呼叫转移应用,反之亦然?


当前回答

以下摘自Michael Bolin的《结束:最终指南》。它可能看起来有点冗长,但它充满了很多洞察力。摘自“附录B.经常误解的JavaScript概念”:


调用函数时所指的内容

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

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引用同一个函数,但它们在调用时的行为不同,因为每次调用中接收方的值都不同。因此,在调用引用此的函数时,务必确保在调用此函数时具有正确的值。要清楚的是,如果函数体中没有引用这一点,那么f(20)和obj.addValues(20)的行为将是相同的。

因为函数是JavaScript中的一流对象,所以它们可以有自己的方法。所有函数都有方法call()和apply(),这使得在调用函数时可以重新定义接收器(即它所指的对象)。方法签名如下:

/**
* @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或未定义。当向call()或apply()提供null或undefined作为接收方时,将使用全局对象作为接收方的值。因此,前面的代码具有相同的不良副作用,即向全局对象添加一个名为value的属性。

将函数视为不知道其所分配的变量可能会有所帮助。这有助于强化这样一种观念,即当调用函数时,而不是定义函数时,它的值将被绑定。


提取结束。

其他回答

有时,一个对象借用另一个对象的函数是有用的,这意味着借用对象只需像执行自己的函数一样执行借出函数。

一个小代码示例:

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

这些方法对于为对象提供临时功能非常有用。

从Function.pr原型.apply()上的MDN文档中:

apply()方法调用具有给定this值的函数作为数组(或类似数组的对象)提供的参数。语法fun.apply(thisArg,[argsArray])

从Function.pr原型.call()上的MDN文档中:

call()方法调用具有给定this值和单独提供的参数的函数。语法fun.call(thisArg[,arg1[,arg2[,…]]])

从JavaScript中的Function.apply和Function.call:

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


代码示例:

var doSomething=函数(){var arr=[];for(参数中的i){if(this〔arguments〔i〕〕的类型!==“undefined”){arr.push(this〔arguments〔i〕〕);}}返回arr;}var输出=函数(位置,对象){document.body.innerHTML+=“<h3>输出”+位置+“</h3>”+JSON.stringify(obj)+“\n<br>\n<br><hr>”;}输出(1,doSomething(“一”,“二”,“二”,“一”));输出(2,doSomething.apply({一:‘Steven’,二:‘Jane’}[“一”,“二”,“二”,“一”]));输出(3,doSomething.call({一:‘Steven’,二:‘Jane’},“一”,“二”,“二”,“一”));

另请参见此Fiddle。

以下摘自Michael Bolin的《结束:最终指南》。它可能看起来有点冗长,但它充满了很多洞察力。摘自“附录B.经常误解的JavaScript概念”:


调用函数时所指的内容

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

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引用同一个函数,但它们在调用时的行为不同,因为每次调用中接收方的值都不同。因此,在调用引用此的函数时,务必确保在调用此函数时具有正确的值。要清楚的是,如果函数体中没有引用这一点,那么f(20)和obj.addValues(20)的行为将是相同的。

因为函数是JavaScript中的一流对象,所以它们可以有自己的方法。所有函数都有方法call()和apply(),这使得在调用函数时可以重新定义接收器(即它所指的对象)。方法签名如下:

/**
* @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或未定义。当向call()或apply()提供null或undefined作为接收方时,将使用全局对象作为接收方的值。因此,前面的代码具有相同的不良副作用,即向全局对象添加一个名为value的属性。

将函数视为不知道其所分配的变量可能会有所帮助。这有助于强化这样一种观念,即当调用函数时,而不是定义函数时,它的值将被绑定。


提取结束。

K.Scott Allen对这件事有很好的评论。

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

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

So:

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

尽管call和apply实现了相同的功能,但我认为至少有一个地方不能使用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