Qual é a diferença entre call e apply?

3105

Qual é a diferença entre usar calle applychamar uma função?

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

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

Existem diferenças de desempenho entre os dois métodos mencionados acima? Quando é melhor usar callrepetidamente applye vice-versa?

John Duff
fonte
727
Pense aem candidatar-se a matriz de argumentos e cem chamar por colunas de argumentos.
Larry Battle
176
@ LarryBattle Eu faço quase o mesmo, mas acho que a aplica-se à matriz ec na chamada por vírgula (ou seja, argumentos separados por vírgula).
Samih 6/12/13
3
Eu concordo que é estúpido. O que é irritante é que, de alguma forma, essa pergunta é feita durante as entrevistas, porque algum idiota influente adicionou a questão à sua lista de perguntas importantes de js.
Ringo
6
Você se candidata a um emprego uma vez (um argumento), liga para as pessoas várias vezes (vários argumentos). Alternativa: existem muitos jogos de Call of Duty.
Gras Double
1
Quando a intenção é invocar uma função variável com uma lista de valores de argumentos, independentemente do valor "this", use o operador de dispersão ES6, por exemplo, fn(...input)onde input é uma matriz. developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Gajus

Respostas:

3649

A diferença é que applypermite invocar a função argumentscomo uma matriz; callrequer que os parâmetros sejam listados explicitamente. Um acelerador é útil " A para um rray e C para C omma."

Consulte a documentação da MDN sobre aplicar e ligar .

Pseudo-sintaxe:

theFunction.apply(valueForThis, arrayOfArgs)

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

Há também, a partir do ES6, a possibilidade de spreado array ser usado com a callfunção, você pode ver as compatibilidades aqui .

Código de amostra:

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

flatline
fonte
25
Uma coisa a acrescentar é que os argumentos devem ser uma matriz numérica ([]). Matrizes associativas ({}) não funcionarão.
21412 Kevin Schroeder
326
@ KevinSchroeder: Na linguagem javascript, []é chamado de matriz , {}é chamado de objeto .
28713 Martijn
89
Costumava esquecer que pega uma matriz e que espera que você liste os argumentos. Uma técnica que eu usei para lembrar que é, se a primeira letra do método começa com um , então ele recebe um array ou seja, um conjunto plicar
Aziz punjani
16
@SAM Usar a chamada em vez de uma chamada de função normal só faz sentido se você precisar alterar o valor disso para a chamada de função. Um exemplo (que converte um objeto de argumento de funções em uma matriz): Array.prototype.slice.call(arguments)ou [].slice.call(arguments). apply faz sentido se você tiver os argumentos em uma matriz, por exemplo, em uma função que chama outra função com (quase) os mesmos parâmetros. Recomendação Use uma chamada de função normal funcname(arg1)se isso for o que você precisa e salve a chamada e solicite as ocasiões especiais em que você realmente precisa delas.
algum
4
@KunalSingh Both calle applyusa dois parâmetros. O primeiro argumento da apply' and função call` deve ser o objeto do proprietário e o segundo parâmetro serão parâmetros separados por matriz ou vírgula, respectivamente. Se você passar nullou undefinedcomo primeiro argumento, em seguida, no modo não-estrito eles são substituídos por objeto global ou sejawindow
AJ Qarshi
229

K. Scott Allen tem uma boa redação sobre o assunto.

Basicamente, eles diferem na maneira como lidam com argumentos de função.

O método apply () é idêntico ao call (), exceto apply () requer uma matriz como o segundo parâmetro. A matriz representa os argumentos para o método de destino ".

Assim:

// assuming you have f
function f(message) { ... }
f.call(receiver, "test");
f.apply(receiver, ["test"]);
notnoop
fonte
42
o segundo parâmetro de apply () e call () é opcional, não obrigatório.
angry kiwi
34
O primeiro parâmetro também não é necessário.
Ikrom
@Ikrom, o primeiro parâmetro não é necessário, callmas é um requisito para apply
iamcastelli 28/01
160

Para responder à parte sobre quando usar cada função, use applyse você não souber o número de argumentos que estará passando ou se eles já estiverem em uma matriz ou objeto semelhante a matriz (como o argumentsobjeto para encaminhar seus próprios argumentos. Use de calloutra forma, pois não há necessidade de agrupar os argumentos em uma matriz.

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 não estou passando nenhum argumento (como o seu exemplo), prefiro calljá que estou chamando a função. applyimplicaria que você está aplicando a função aos argumentos (inexistentes).

Não deve haver diferenças de desempenho, exceto talvez se você usar applye agrupar os argumentos em uma matriz (por exemplo, em f.apply(thisObject, [a, b, c])vez de f.call(thisObject, a, b, c)). Eu não o testei, então pode haver diferenças, mas seria muito específico do navegador. É provável que callseja mais rápido se você ainda não tiver os argumentos em uma matriz e applymais rápido se tiver.

Matthew Crumley
fonte
111

Aqui está um bom mnemônico. A plicar usa A rrays e A lways leva um ou dois argumentos. Quando você usa C, tudo o que você precisa é C ou o número de argumentos.

Joe
fonte
2
Mnemônico útil aqui! Vou mudar o 'um ou dois argumentos' para dizer 'um máximo de dois argumentos', pois nem o primeiro nem o segundo parâmetro applyé necessário. Não sei ao certo por que alguém chamará applyou callsem um parâmetro. Parece que alguém está tentando descobrir por que aqui stackoverflow.com/questions/15903782/…
dantheta
92

Embora esse seja um tópico antigo, eu só queria salientar que o .call é um pouco mais rápido que o .apply. Eu não posso te dizer exatamente o porquê.

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


[ UPDATE!]

Douglas Crockford menciona brevemente a diferença entre os dois, o que pode ajudar a explicar a diferença de desempenho ... http://youtu.be/ya4UHuXNygM?t=15m52s

Apply usa uma matriz de argumentos, enquanto Call usa zero ou mais parâmetros individuais! Ah, ah!

.apply(this, [...])

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

kmatheny
fonte
Isso depende do que a função faz com os parâmetros / matriz, se não precisar processar a matriz, leva menos tempo?
Eric Hodonsky
12
Curiosamente, mesmo sem a matriz, a chamada ainda é muito mais rápida. jsperf.com/applyvscallvsfn2
Josh Mc
@ JoshMc Isso seria muito específico do navegador. No IE 11, estou conseguindo aplicar duas vezes mais rápido que a chamada.
Vincent McNabb
1
1. Criar uma nova matriz significa que o coletor de lixo precisará limpá-la em algum momento. 2. Acessar itens na matriz usando a desreferência é menos eficiente do que acessar diretamente uma variável (parâmetro). (Acredito que é isso que kmatheny quis dizer com "análise", que na verdade é algo bem diferente.) Mas nenhum dos meus argumentos explica o jsperf. Isso deve estar relacionado à implementação do mecanismo das duas funções, por exemplo, talvez elas criem uma matriz vazia de qualquer maneira, se nenhuma foi aprovada.
Joeytwiddle
Obrigado por compartilhar o teste e vídeo
Gary
76

Segue um extrato de Closure: The Definitive Guide, de Michael Bolin . Pode parecer um pouco demorado, mas está saturado de muita percepção. Do "Apêndice B. Conceitos de JavaScript frequentemente incompreendidos":


O que thisse refere quando uma função é chamada

Ao chamar uma função do formulário foo.bar.baz(), o objeto foo.baré chamado de receptor. Quando a função é chamada, é o receptor que é usado como o valor para 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 não houver um receptor explícito quando uma função é chamada, o objeto global se torna o receptor. Conforme explicado em "goog.global" na página 47, window é o objeto global quando o JavaScript é executado em um navegador da web. Isso leva a um comportamento surpreendente:

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

Mesmo assim, obj.addValuese se freferem à mesma função, eles se comportam de maneira diferente quando chamados, porque o valor do receptor é diferente em cada chamada. Por esse motivo, ao chamar uma função que se refere this, é importante garantir que thiso valor correto seja chamado. Para ser claro, se thisnão fosse referenciado no corpo da função, o comportamento de f(20)e obj.addValues(20)seria o mesmo.

Como as funções são objetos de primeira classe em JavaScript, elas podem ter seus próprios métodos. Todas as funções possuem os métodos call()e apply()que permitem redefinir o receptor (ou seja, o objeto a que thisse refere) ao chamar a função. As assinaturas de método são as seguintes:

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

Observe que a única diferença entre call()e apply()é que call()recebe os parâmetros da função como argumentos individuais, enquanto os apply()recebe como uma única matriz:

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

As seguintes chamadas são equivalentes, como fe obj.addValuesse referem à mesma função:

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

No entanto, como call()nem apply()usa o valor de seu próprio receptor para substituir o argumento do receptor quando não for especificado, o seguinte não funcionará:

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

O valor de thisnunca pode ser nullou undefinedquando uma função é chamada. Quando nullou undefinedé fornecido como receptor para call()ou apply(), o objeto global é usado como o valor para o receptor. Portanto, o código anterior tem o mesmo efeito colateral indesejável de adicionar uma propriedade nomeada valueao objeto global.

Pode ser útil pensar em uma função como não tendo conhecimento da variável à qual está atribuída. Isso ajuda a reforçar a ideia de que o valor disso será vinculado quando a função for chamada e não quando for definida.


Fim da extração.

Dominykas Mostauskis
fonte
Apenas a nota do fato, que additionalValuesnão é referenciado dentro obj.addValuesdo corpo
Viktor Stolbin
Eu sei que você estava respondendo à pergunta, mas gostaria de acrescentar: você poderia ter usado o bind ao definir f. var f = obj.addValues;torna var f = obj.addValues.bind(obj) - se e agora f (20) funcionaria sem ter que usar a chamada ou aplicar todas as vezes.
jhliberty
Sei que você não escreveu, mas destacou o texto e os exemplos do livro como relevantes, e estou muito agradecido. Eles foram muito úteis.
Fralcon
34

Às vezes, é útil que um objeto peça emprestada a função de outro objeto, o que significa que o objeto emprestado simplesmente executa a função emprestada como se fosse sua.

Um pequeno exemplo de código:

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

Esses métodos são muito úteis para fornecer aos objetos funcionalidade temporária.

tjacks3
fonte
1
Para as pessoas que querem saber como ver o console.logcheck-out: O que é console.log e como eu o uso?
Michel Ayres
25

Outro exemplo com Call, Apply e Bind. A diferença entre Call e Apply é evidente, mas o Bind funciona assim:

  1. Bind retorna uma instância de uma função que pode ser executada
  2. O primeiro parâmetro é ' this '
  3. O segundo parâmetro é uma lista de argumentos separados por vírgula (como Chamada )

}

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
*/
Mahesh
fonte
23

Gostaria de mostrar um exemplo, onde o argumento 'valueForThis' é usado:

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] 

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


fonte
20

Call () recebe argumentos separados por vírgula, ex:

.call(scope, arg1, arg2, arg3)

e apply () recebe uma matriz de argumentos, ex:

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

Aqui estão mais alguns exemplos de uso: http://blog.i-evaluation.com/2012/08/15/javascript-call-and-apply/

Mark Karwowski
fonte
`// chama () === argumentos separados por vírgula (lista de argumentos). call (this, args1, args2, args3, ...) // apply () === array de argumentos (array-items). apply (this, [arr0, arr1, arr2, ...]) `
xgqfrms
19

Nos documentos MDN sobre Function.prototype.apply () :

O método apply () chama uma função com um determinado thisvalor e argumentos fornecidos como uma matriz (ou um objeto semelhante a uma matriz).

Sintaxe

fun.apply(thisArg, [argsArray])

Nos documentos MDN sobre Function.prototype.call () :

O método call () chama uma função com um determinado thisvalor e argumentos fornecidos individualmente.

Sintaxe

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

Em Function.apply e Function.call em JavaScript :

O método apply () é idêntico ao call (), exceto apply () requer uma matriz como o segundo parâmetro. A matriz representa os argumentos para o método de destino.


Exemplo de código:

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

Veja também este violino .

John Slegers
fonte
10

Aqui está um post pequeno, escrevi sobre isso:

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"
Dan
fonte
aqui está outro: blog.i-evaluation.com/2012/08/15/javascript-call-and-apply, mas basicamente está certo: .call (escopo, arg1, arg2, arg3)
Mark Karwowski
7

A diferença é que call()leva os argumentos da função separadamente e apply()leva os argumentos da função em uma matriz.

Sanjib Debnath
fonte
6

Podemos diferenciar chamadas e aplicar métodos como abaixo

CHAMADA: Uma função com argumento fornece individualmente. Se você conhece os argumentos a serem transmitidos ou não há argumentos a serem transmitidos, pode usar call.

APLICAR: Chame uma função com argumento fornecido como uma matriz. Você pode usar o apply se não souber quantos argumentos serão transmitidos para a função.

Há uma vantagem de usar a aplicação sobre chamada, não precisamos alterar o número de argumentos, apenas podemos alterar uma matriz que é passada.

Não há grande diferença no desempenho. Mas podemos dizer que a chamada é um pouco mais rápida do que comparar para aplicar, porque uma matriz precisa avaliar no método apply.

Praveen D
fonte
5

A diferença entre esses métodos e os métodos é como você deseja passar os parâmetros.

"A para array e C para vírgula" é um mnemônico útil.

venkat7668
fonte
11
O que essa resposta fornece que ainda não está bem fornecida em outras respostas?
Kyll
5

Chamar e aplicar ambos são usados ​​para forçar o thisvalor quando uma função é executada. A única diferença é que callrecebe n+1argumentos onde 1 é thise 'n' arguments. applyleva apenas dois argumentos, um é thiso outro é matriz de argumentos.

A vantagem que vejo applyacima callé que podemos delegar facilmente uma chamada de função para outra função sem muito esforço;

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

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

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

Observe como é fácil delegado hellopara sayHellousar apply, mas com callisso é muito difícil de alcançar.

Raghavendra
fonte
4

Mesmo assim calle applyobtendo a mesma coisa, acho que há pelo menos um lugar onde você não pode usar, callmas só pode usá-lo apply. É quando você deseja dar suporte à herança e deseja chamar o construtor.

Aqui está uma função que permite criar classes que também suporta a criação de classes estendendo outras classes.

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
Dhana Krishnasamy
fonte
Acredito que a chamada funcionaria lá com o operador de spread, conforme descrito na resposta selecionada. A menos que eu esteja perdendo alguma coisa.
jhliberty
4

Resumo:

Ambos call()e apply()são métodos localizados em Function.prototype. Portanto, eles estão disponíveis em todos os objetos de função através da cadeia de protótipos. Ambos call()e apply()podem executar uma função com um valor especificado de this.

A principal diferença entre call()e apply()é a maneira como você deve passar argumentos para ele. Em ambos call()e apply()você passa como primeiro argumento o objeto para o qual deseja ser o valor this. Os outros argumentos diferem da seguinte maneira:

  • Com call()você tem que colocar os argumentos normalmente (a partir do segundo argumento)
  • Com apply()você tem que passar na matriz de argumentos.

Exemplo:

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

Por que eu precisaria usar essas funções?

O thisvalor pode ser complicado às vezes em javascript. O valor de thisdeterminado quando uma função é executada e não quando uma função é definida. Se nossa função depende de uma thisligação correta , podemos usar call()e apply()impor esse comportamento. Por exemplo:

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

Willem van der Veen
fonte
4

A principal diferença é que, usando call, podemos alterar o escopo e passar argumentos normalmente, mas apply permite chamá-lo usando argumentos como uma matriz (passá-los como uma matriz). Mas em termos do que eles devem fazer no seu código, eles são bem parecidos.

Enquanto a sintaxe dessa função é quase idêntica à de apply (), a diferença fundamental é que call () aceita uma lista de argumentos, enquanto apply () aceita uma única matriz de argumentos.

Então, como você vê, não há uma grande diferença, mas ainda assim, há casos em que preferimos usar call () ou apply (). Por exemplo, observe o código abaixo, que encontra o menor e o maior número em uma matriz do MDN, usando o método 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)

Portanto, a principal diferença é a maneira como transmitimos os argumentos

:

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

Aplique:

function.apply(thisArg, [argsArray]);
Alireza
fonte
2

Deixe-me adicionar um pequeno detalhe a isso.

essas duas chamadas são quase equivalentes:

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

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

Há apenas uma pequena diferença:

  • O spreadoperador ... permite passar iterável args como a lista a ser chamada.
  • O applyaceita apenas argumentos do tipo array .

Portanto, essas chamadas se complementam. Onde esperamos um iterável , callfunciona, onde esperamos um array , applyfunciona.

E para objetos que são tanto iterable e gama-like , como uma matriz real, nós tecnicamente poderia usar qualquer um deles, mas aplicam-se provavelmente será mais rápido porque a maioria dos motores de JavaScript otimizá-lo internamente melhor.

Pravin Divraniya
fonte