JavaScript: As diferenças entre os métodos Call e Apply
Introdução
Neste artigo irei demonstrar duas formas de invocação de métodos. As implementações call
e apply
possibilitam informar o contexto do método e executá-lo imediatamente, seguindo uma abordagem diferente do método bind
que prepara a execução informando o contexto e os parâmetros.
Como funciona?
Os métodos call
e apply
facilitam que você escreva um método e componha outros objetos, uma boa definição extraída do Livro JavaScript Patterns é que esta feature específica da linguagem JavaScript proporciona emprestar um método, onde você pode definir uma função atribuída no contexto global sem mesmo ter um objeto ou classe como owner
, claro que atribuir ao contexto global funções e objetos não é uma boa prática, porém você pode tomar emprestado métodos de outros objetos ou classes.
Diferenças entre o Call x Apply
Ambos resolvem o mesmo problema na linguagem JavaScript, como foi definido anteriormente emprestando métodos.
A real diferença está nos parâmetros, Apply
invoca uma função com o this (contexto) e um array com os parâmetros da função, já o Call
utiliza como primeiro parâmetro o this (contexto) e os próximos são os parâmetros da função, veja abaixo as definições dos métodos.
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
Ok, agora na prática.
Serão definidos métodos que serão emprestados, utilizando os métodos apresentados call
e apply
. A classe ShopCart toma emprestado os métodos notifyClient
, sendMail
e getDeals
, neste cenário foram definidos métodos em um contexto global, porém estes métodos poderiam ser de um objeto ou classe.
Foram criados dois exemplos seguindo os padrões EcmaScript (ES5 e ES6).
ES5
function notifyClient(message) {
alert(message);
}
function sendMail(customer, total) {
notifyClient.call(this, 'TODO: Send mail, (' + String(customer.id) + ', ' + String(total) + ')');
}
function getDeals() {
var product = arguments[0];
var minPrice = arguments[1];
product.price = Math.round(product.price * 0.9, 2);
if(product.price < minPrice) {
product.price = minPrice;
}
}
function ShopCart(){
}
ShopCart.prototype.items = [];
ShopCart.prototype.customer = { id: 1 };
ShopCart.prototype.total = function total() {
var total = 0;
var item;
for(var i=0, c=this.items.length; i<c; i++){
item = this.items[i];
total += item.price * item.quantity;
}
return total;
};
ShopCart.prototype.addItem = function addItem(product) {
this.getDealsByProduct(product);
this.items.push(product);
notifyClient.call(this, 'Item added ' + product.name);
};
ShopCart.prototype.getDealsByProduct = function getDealsByProduct(product) {
getDeals.apply(this, [product, product.price * 0.6]);
};
ShopCart.prototype.placeOrder = function placeOrder() {
sendMail.apply(this, [this.customer, this.total()]);
};
var shopCart = new ShopCart();
shopCart.addItem({
id: 1,
name: 'Xbox Game',
price: 179,
quantity: 1
});
shopCart.placeOrder();
ES6
function notifyClient(message) {
alert(message);
}
function sendMail(customer, total) {
notifyClient.call(this, `TODO: Send mail, (${String(customer.id)}, ${String(total)})`);
}
function getDeals() {
var product = arguments[0];
var minPrice = arguments[1];
product.price = Math.round(product.price * 0.9, 2);
if(product.price < minPrice) {
product.price = minPrice;
}
}
class ShopCart{
constructor() {
this.items = [];
this.customer = { id: 1 };
}
total() {
var total = 0;
var item;
for(var i=0, c=this.items.length; i<c; i++){
item = this.items[i];
total += item.price * item.quantity;
}
return total;
}
addItem(product) {
this.getDealsByProduct(product);
this.items.push(product);
notifyClient.call(this, 'Item added ' + product.name);
}
getDealsByProduct(product) {
getDeals.apply(this, [product, product.price * 0.6]);
}
placeOrder() {
sendMail.apply(this, [this.customer, this.total()]);
}
};
var shopCart = new ShopCart();
shopCart.addItem({
id: 1,
name: 'Xbox Game',
price: 179.00,
quantity: 1
});
shopCart.placeOrder();
A variável Arguments
Outro recurso interessante é a variável arguments
que se trata de um array dos argumentos, definido em toda a função JavaScript, esta é outra forma de obter os parâmetros, esta implementação foi realizada no método getDeals do exemplo acima.
Conclusão
Estes são recursos existentes no JavaScript desde a versão EcmaScript 3 (ES3). A linguagem JavaScript possui conceitos bem diferentes em relação às outras linguagens que flexibilizam o desenvolvimento de software, você pode optar por herança ou composição de objetos. Sendo assim escolha qual abordagem se enquadra em sua solução de software.