Qual'è la differenza tra call e apply?


3105

Qual è la differenza tra usare calle applyinvocare una funzione?

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

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

Ci sono differenze di prestazioni tra i due metodi sopra menzionati? Quando è meglio usare calloltre applye viceversa?


727
Pensa a ain apply per array di args e cin call per colonne di args.
Larry Battle,

176
@LarryBattle Faccio quasi lo stesso, ma penso che in in si applichi array e c in call per virgola (ovvero argomenti separati da virgola).
Samih,

3
Sono d'accordo che è stupido. La cosa fastidiosa è che in qualche modo questa domanda viene posta durante le interviste perché alcuni sciocchi influenti hanno aggiunto la domanda alla loro lista di importanti domande js.
Ringo

6
Fai domanda per un lavoro una volta (un argomento), [telefono] chiami molte persone (più argomenti). Alternativa: ci sono [troppo?] Molti giochi di Call of Duty.
Gras Double

1
Quando l'intenzione è quella di invocare una funzione variadica con un elenco di valori argomento indipendentemente dal valore "questo", utilizzare l'operatore spread ES6, ad es. fn(...input)Dove input è un array. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Gajus

Risposte:


3649

La differenza è che applyti permette di invocare la funzione con argumentsun array; callrichiede che i parametri siano elencati esplicitamente. Un utile mnemonico è " A per un rray e C per c omma".

Vedere la documentazione di MDN sulla applicare e chiamata .

Sintassi pseudo:

theFunction.apply(valueForThis, arrayOfArgs)

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

V'è anche, come di ES6, la possibilità di spreadmatrice per l'utilizzo con la callfunzione, è possibile vedere le compatibilità qui .

Codice di esempio:

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


25
Una cosa da aggiungere è che gli arg devono essere una matrice numerica ([]). Gli array associativi ({}) non funzioneranno.
Kevin Schroeder,

326
@KevinSchroeder: nel linguaggio javascript, []si chiama un array , {}si chiama un oggetto .
Martijn,

89
Spesso dimenticavo quale prende un array e quale si aspetta che tu elenchi gli argomenti. Una tecnica che ho usato per ricordare è che se la prima lettera del metodo inizia con un allora prende un array cioè un array pply
aziz punjani

16
@SAM L'uso della chiamata invece di una normale chiamata di funzione ha senso solo se è necessario modificare il valore di questa per la chiamata di funzione. Un esempio (che converte un oggetto argomenti-funzioni in un array): Array.prototype.slice.call(arguments)o [].slice.call(arguments). applicare ha senso se si hanno gli argomenti in un array, ad esempio in una funzione che chiama un'altra funzione con (quasi) gli stessi parametri. Raccomandazione Utilizzare una normale chiamata di funzione funcname(arg1)se ciò fa ciò di cui si ha bisogno, quindi salvare la chiamata e applicare per le occasioni speciali in cui sono realmente necessarie.
circa il

4
@KunalSingh Both calle applyaccetta due parametri. Il primo argomento della apply' and funzione call` deve essere l'oggetto proprietario e il secondo parametro saranno rispettivamente parametri separati da array o virgola. Se si passa nullo undefinedcome primo argomento, in modalità non rigorosa vengono sostituiti con l'oggetto globale, ovverowindow
AJ Qarshi

229

K. Scott Allen ha un bel resoconto sulla questione.

Fondamentalmente, differiscono su come gestiscono gli argomenti delle funzioni.

Il metodo apply () è identico a call (), ad eccezione di apply () richiede un array come secondo parametro. La matrice rappresenta gli argomenti per il metodo target. "

Così:

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

42
il secondo parametro di apply () e call () è facoltativo, non richiesto.
kiwi arrabbiato,

34
Anche il primo parametro non è richiesto.
Ikrom,

@Ikrom, il primo parametro non è richiesto callma un requisito per apply
iamcastelli il

160

Per rispondere alla parte su quando utilizzare ciascuna funzione, utilizzare applyse non si conosce il numero di argomenti che si passerà o se si trovano già in un array o un oggetto simile a un array (come l' argumentsoggetto per inoltrare i propri argomenti. Utilizzare callaltrimenti, poiché non è necessario racchiudere gli argomenti in un array.

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

Quando non sto passando alcun argomento (come il tuo esempio), preferisco calldato che sto chiamando la funzione. applyimplicherebbe che stai applicando la funzione agli argomenti (inesistenti).

Non dovrebbero esserci differenze di prestazioni, tranne forse se si utilizzano applye si avvolgono gli argomenti in un array (ad es. f.apply(thisObject, [a, b, c])Invece di f.call(thisObject, a, b, c)). Non l'ho provato, quindi potrebbero esserci delle differenze, ma sarebbe molto specifico per il browser. È probabile che callsia più veloce se non si hanno già gli argomenti in un array e applyse è più veloce.


111

Ecco un buon mnemonico. Un pplicare utilizza A rrays e A lways prende uno o due argomenti. Quando si utilizza C tutto ciò che dovete C ount il numero di argomenti.


2
Utile mnemonico proprio lì !. Cambierò 'uno o due Argomenti' per dire 'un massimo di due Argomenti' poiché non applysono richiesti né il primo né il secondo parametro . Non sono sicuro però perché uno chiamerà applyo callsenza un parametro. Sembra che qualcuno sta cercando di scoprire perché qui stackoverflow.com/questions/15903782/...
dantheta

92

Anche se questo è un vecchio argomento, volevo solo sottolineare che .call è leggermente più veloce di .apply. Non posso dirti esattamente perché.

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


[ UPDATE!]

Douglas Crockford menziona brevemente la differenza tra i due, il che può aiutare a spiegare la differenza di prestazioni ... http://youtu.be/ya4UHuXNygM?t=15m52s

Apply accetta una serie di argomenti, mentre Call accetta zero o più parametri singoli! Ah ah!

.apply(this, [...])

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


Questo dipende da cosa fa la funzione con i parametri / array, se non è necessario elaborare l'array, ci vuole meno tempo?
Eric Hodonsky,

12
È interessante notare che anche senza l'array, la chiamata è ancora molto più veloce. jsperf.com/applyvscallvsfn2
Josh Mc

@JoshMc Sarebbe molto specifico per browser. In IE 11, sto facendo domanda andando due volte più veloce della chiamata.
Vincent McNabb l'

1
1. La creazione di un nuovo array significa che il garbage collector dovrà pulirlo ad un certo punto. 2. L'accesso agli elementi dell'array mediante la dereference è meno efficiente dell'accesso diretto a una variabile (parametro). (Credo che questo sia ciò che kmatheny intendeva per "analisi", che in realtà è qualcosa di completamente diverso.) Ma nessuno dei miei argomenti spiega il jsperf. Ciò deve essere correlato all'implementazione delle due funzioni da parte del motore, ad esempio forse creano comunque un array vuoto, se non ne viene passato nessuno.
joeytwiddle

Grazie per aver condiviso il test e il video
Gary,

76

Segue un estratto da Closure: The Definitive Guide di Michael Bolin . Potrebbe sembrare un po 'lungo, ma è saturo di molte intuizioni. Da "Appendice B. Concetti JavaScript spesso fraintesi":


Cosa si thisriferisce a quando viene chiamata una funzione

Quando si chiama una funzione del modulo foo.bar.baz(), l'oggetto foo.barviene indicato come destinatario. Quando viene chiamata la funzione, è il ricevitore che viene utilizzato come valore per 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);

Se non è presente un destinatario esplicito quando viene chiamata una funzione, l'oggetto globale diventa il destinatario. Come spiegato in "goog.global" a pagina 47, window è l'oggetto globale quando JavaScript viene eseguito in un browser web. Questo porta ad alcuni comportamenti sorprendenti:

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

Anche se obj.addValuesef si riferiscono alla stessa funzione, si comportano in modo diverso quando chiamato perché il valore del ricevitore è diverso in ciascuna chiamata. Per questo motivo, quando si chiama una funzione a cui si fa riferimento this, è importante assicurarsi che thisabbia il valore corretto quando viene chiamata. Per essere chiari, se thisnon si facesse riferimento nel corpo della funzione, il comportamento f(20)e obj.addValues(20)sarebbe lo stesso.

Poiché le funzioni sono oggetti di prima classe in JavaScript, possono avere i propri metodi. Tutte le funzioni hanno i metodicall() e apply()che rendono possibile ridefinire il ricevitore (cioè l'oggetto a cui si thisfa riferimento) quando si chiama la funzione. Le firme del metodo sono le seguenti:

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

Si noti che l'unica differenza tra call()eapply() è che call()riceve i parametri della funzione come singoli argomenti, mentre apply()li riceve come un singolo array:

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

Le seguenti chiamate sono equivalenti, come f e obj.addValuesriferiscono alla stessa funzione:

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

Tuttavia, poiché nessuno dei due call()apply()utilizza né il valore del proprio ricevitore per sostituire l'argomento del ricevitore quando non è specificato, non funzionerà quanto segue:

// Both statements evaluate to NaN
obj.addValues.call(undefined, 10, 20, 30);
obj.addValues.apply(undefined, [10, 20, 30]);

Il valore di thisnon può mai essere nullo undefinedquando viene chiamata una funzione. quandonull o undefinedviene fornito come destinatario a call()o apply(), l'oggetto globale viene invece utilizzato come valore per il destinatario. Pertanto, il codice precedente ha lo stesso effetto collaterale indesiderato dell'aggiunta di una proprietà denominata valuenell'oggetto globale.

Può essere utile pensare a una funzione come a non avere conoscenza della variabile a cui è assegnata. Questo aiuta a rafforzare l'idea che il valore di questo sarà associato quando viene chiamata la funzione piuttosto che quando viene definita.


Fine dell'estratto.


Solo per notare il fatto, a cui additionalValuesnon si fa riferimento all'interno del obj.addValuescorpo
Viktor Stolbin il

So che stavi rispondendo alla domanda ma che vorresti aggiungere: avresti potuto usare il bind durante la definizione di f. var f = obj.addValues;diventa var f = obj.addValues.bind(obj) e ora f (20) funzionerebbe senza dover usare call o applicare ogni volta.
jhliberty,

So che non l'hai scritto, ma hai messo in evidenza il testo e gli esempi del libro come pertinenti, e sono molto grato. Sono stati molto utili.
Fralcon,

34

A volte è utile che un oggetto prenda in prestito la funzione di un altro oggetto, nel senso che l'oggetto mutuante esegue semplicemente la funzione prestata come se fosse la sua.

Un piccolo esempio di codice:

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

Questi metodi sono molto utili per dare agli oggetti funzionalità temporanea.


1
Per le persone che vogliono sapere come fare il console.logcheck out: Cos'è console.log e come lo uso?
Michel Ayres,

25

Un altro esempio con Call, Apply e Bind. La differenza tra Call e Apply è evidente, ma Bind funziona in questo modo:

  1. Bind restituisce un'istanza di una funzione che può essere eseguita
  2. Il primo parametro è ' questo '
  3. Il secondo parametro è un elenco di argomenti separato da virgole (come 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

Vorrei mostrare un esempio, in cui viene utilizzato l'argomento '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] 

** dettagli: http://es5.github.io/#x15.4.4.7 *


20

Call () accetta argomenti separati da virgola, ad esempio:

.call(scope, arg1, arg2, arg3)

e apply () accetta una serie di argomenti, ad esempio:

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

ecco alcuni altri esempi di utilizzo: http://blog.i-evaluation.com/2012/08/15/javascript-call-and-apply/


`// call () === argomenti separati da virgola (lista-argomenti) .call (this, args1, args2, args3, ...) // apply () === array di argomenti (array-items). si applica (questo, [arr0, arr1, arr2, ...]) `
xgqfrms il

19

Dai documenti MDN su Function.prototype.apply () :

Il metodo apply () chiama una funzione con un dato this valore e argomenti forniti come una matrice (o un oggetto simile a una matrice).

Sintassi

fun.apply(thisArg, [argsArray])

Dai documenti MDN su Function.prototype.call () :

Il metodo call () chiama una funzione con un dato thisvalore e argomenti forniti singolarmente.

Sintassi

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

Da Function.apply e Function.call in JavaScript :

Il metodo apply () è identico a call (), ad eccezione di apply () richiede un array come secondo parametro. La matrice rappresenta gli argomenti per il metodo target.


Esempio di codice:

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

Vedi anche questo violino .



10

Ecco un post di piccole dimensioni, ho scritto su questo:

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"

eccone un altro: blog.i-evaluation.com/2012/08/15/javascript-call-and-apply ma sostanzialmente è giusto: .call (scope, arg1, arg2, arg3)
Mark Karwowski

7

La differenza è che call()prende gli argomenti della funzione separatamente e apply()accetta gli argomenti della funzione in un array.


6

Possiamo differenziare le chiamate e applicare i metodi come di seguito

CALL: una funzione con argomento fornisce singolarmente. Se conosci gli argomenti da passare o non ci sono argomenti da passare puoi usare call.

APPLICA: chiama una funzione con argomento fornito come matrice. Puoi usare applicare se non sai quanti argomenti passeranno alla funzione.

C'è un vantaggio nell'uso di apply over call, non abbiamo bisogno di cambiare il numero di argomenti, ma possiamo cambiare un array che viene passato.

Non c'è grande differenza nelle prestazioni. Ma possiamo dire che la chiamata è un po 'più veloce rispetto al confronto da applicare perché un array deve valutare nel metodo apply.


5

Le differenze tra questi e i metodi sono: come si desidera passare i parametri.

"A per array e C per virgola" è un pratico mnemonico.


11
Cosa fornisce questa risposta che non è già ben fornita in altre risposte?
Kyll

5

Chiama e applica entrambi sono usati per forzare il thisvalore quando viene eseguita una funzione. L'unica differenza è che callaccetta n+1argomenti dove 1 è thise 'n' arguments. applyaccetta solo due argomenti, uno è thisl'altro è array di argomenti.

Il vantaggio che vedo applysopra callè che possiamo facilmente delegare una chiamata di funzione ad altre funzioni senza troppi sforzi;

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

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

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

Osservare come facilmente si delegati helloa sayHelloutilizzare apply, ma con callquesto è molto difficile da raggiungere.


4

Anche se calle applyraggiungere la stessa cosa, penso che ci sia almeno un posto dove non puoi usare callma puoi solo usare apply. Questo è quando vuoi supportare l'ereditarietà e vuoi chiamare il costruttore.

Ecco una funzione che consente di creare classi che supporta anche la creazione di classi estendendo altre classi.

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

Credo che la chiamata funzionerebbe lì con l'operatore di diffusione come descritto nella risposta selezionata. A meno che non mi manchi qualcosa.
jhliberty,

4

Sommario:

Entrambi call()e apply()sono metodi su cui si trovano Function.prototype. Pertanto sono disponibili su ogni oggetto funzione tramite la catena prototipo. Entrambi call()e apply()possono eseguire una funzione con un valore specificato dithis .

La differenza principale tra call()e apply()è il modo in cui devi passare degli argomenti al suo interno. In entrambi call()e apply()si passa come primo argomento l'oggetto che si desidera essere il valore come this. Gli altri argomenti differiscono nel modo seguente:

  • Con call()devi inserire gli argomenti normalmente (a partire dal secondo argomento)
  • Con apply()te devi passare in una serie di argomenti.

Esempio:

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

Perché dovrei usare queste funzioni?

Il thisvalore può essere complicato a volte in javascript. Il valore di thisdeterminato quando una funzione viene eseguita non quando viene definita una funzione. Se la nostra funzione dipende da un giusto thislegame, possiamo usare call()e apply()applicare questo comportamento. Per esempio:

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

La differenza principale è che, usando call, possiamo cambiare l'ambito e passare gli argomenti normalmente, ma apply ti consente di chiamarlo usando argomenti come un array (passandoli come un array). Ma in termini di cosa devono fare nel tuo codice, sono abbastanza simili.

Mentre la sintassi di questa funzione è quasi identica a quella di apply (), la differenza fondamentale è che call () accetta un elenco di argomenti, mentre apply () accetta un singolo array di argomenti.

Quindi, come vedi, non c'è una grande differenza, ma ci sono comunque casi che preferiamo usare call () o apply (). Ad esempio, guarda il codice seguente, che trova il numero più piccolo e più grande in un array da MDN, usando il metodo apply:

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

Quindi la differenza principale sta nel modo in cui passiamo gli argomenti:

Chiama:

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

Applicare:

function.apply(thisArg, [argsArray]);

2

Vorrei aggiungere un piccolo dettaglio a questo.

queste due chiamate sono quasi equivalenti:

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

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

C'è solo una piccola differenza:

  • L' spreadoperatore ... consente di passare iterabile args come l'elenco da chiamare.
  • L' applyaccetta solo array come args.

Quindi, queste chiamate si completano a vicenda. Dove ci aspettiamo un iterabile , callfunziona, dove ci aspettiamo un array simile , applyfunziona.

E per oggetti che sono sia iterabili che simili a array , come un array reale, tecnicamente potremmo usarne uno qualsiasi, ma l' applicazione sarà probabilmente più veloce perché la maggior parte dei motori JavaScript lo ottimizzano internamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.