Existem vários aspectos para sobrecarga de argumento em Javascript:
Argumentos variáveis - você pode passar diferentes conjuntos de argumentos (em tipo e quantidade) e a função se comportará de uma maneira que corresponda aos argumentos passados a ela.
Argumentos padrão - você pode definir um valor padrão para um argumento se ele não for passado.
Argumentos nomeados - a ordem dos argumentos torna-se irrelevante e você apenas nomeia quais argumentos deseja passar para a função.
Abaixo está uma seção sobre cada uma dessas categorias de tratamento de argumentos.
Argumentos Variáveis
Como o javascript não tem verificação de tipo em argumentos ou quantidade necessária de argumentos, você pode ter apenas uma implementação myFunc()que pode se adaptar aos argumentos que foram passados a ele verificando o tipo, presença ou quantidade de argumentos.
jQuery faz isso o tempo todo. Você pode tornar alguns dos argumentos opcionais ou pode ramificar em sua função, dependendo de quais argumentos são passados para ela.
Ao implementar esses tipos de sobrecargas, você tem várias técnicas diferentes que pode usar:
- Você pode verificar a presença de qualquer argumento, verificando se o valor do nome do argumento declarado é
undefined.
- Você pode verificar a quantidade total ou argumentos com
arguments.length.
- Você pode verificar o tipo de qualquer argumento.
- Para números variáveis de argumentos, você pode usar a
argumentspseudo-matriz para acessar qualquer argumento com arguments[i].
aqui estão alguns exemplos:
Vejamos o obj.data()método jQuery . Ele suporta quatro formas diferentes de uso:
obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);
Cada um aciona um comportamento diferente e, sem usar essa forma dinâmica de sobrecarga, exigiria quatro funções distintas.
Veja como se pode discernir entre todas essas opções em inglês e, em seguida, combinarei todas no código:
// get the data element associated with a particular key value
obj.data("key");
Se o primeiro argumento passado para .data()for uma string e o segundo argumento for undefined, então o chamador deve usar este formulário.
// set the value associated with a particular key
obj.data("key", value);
Se o segundo argumento não for indefinido, defina o valor de uma chave específica.
// get all keys/values
obj.data();
Se nenhum argumento for passado, retorne todas as chaves / valores em um objeto retornado.
// set all keys/values from the passed in object
obj.data(object);
Se o tipo do primeiro argumento for um objeto simples, defina todas as chaves / valores desse objeto.
Veja como você pode combinar tudo isso em um conjunto de lógica javascript:
// method declaration for .data()
data: function(key, value) {
if (arguments.length === 0) {
// .data()
// no args passed, return all keys/values in an object
} else if (typeof key === "string") {
// first arg is a string, look at type of second arg
if (typeof value !== "undefined") {
// .data("key", value)
// set the value for a particular key
} else {
// .data("key")
// retrieve a value for a key
}
} else if (typeof key === "object") {
// .data(object)
// set all key/value pairs from this object
} else {
// unsupported arguments passed
}
},
A chave para essa técnica é certificar-se de que todas as formas de argumentos que você deseja aceitar sejam identificáveis de forma única e que nunca haja confusão sobre qual formulário o chamador está usando. Isso geralmente requer ordenar os argumentos de forma adequada e certificar-se de que haja exclusividade suficiente no tipo e na posição dos argumentos para que você sempre possa saber qual forma está sendo usada.
Por exemplo, se você tem uma função que recebe três argumentos de string:
obj.query("firstArg", "secondArg", "thirdArg");
Você pode facilmente tornar o terceiro argumento opcional e detectar facilmente essa condição, mas você não pode tornar apenas o segundo argumento opcional porque você não pode dizer qual deles o chamador pretende passar porque não há como identificar se o segundo argumento deve ser o segundo argumento ou o segundo argumento foi omitido, então o que está no lugar do segundo argumento é na verdade o terceiro argumento:
obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");
Como todos os três argumentos são do mesmo tipo, você não pode dizer a diferença entre os diferentes argumentos, portanto não sabe o que o chamador pretendia. Com este estilo de chamada, apenas o terceiro argumento pode ser opcional. Se você quisesse omitir o segundo argumento, ele teria que ser passado como null(ou algum outro valor detectável) e seu código detectaria que:
obj.query("firstArg", null, "thirdArg");
Aqui está um exemplo jQuery de argumentos opcionais. ambos os argumentos são opcionais e assumem os valores padrão se não forem passados:
clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},
Aqui está um exemplo de jQuery em que o argumento pode estar ausente ou qualquer um dos três tipos diferentes que fornecem quatro sobrecargas diferentes:
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
// See if we can take a shortcut and just use innerHTML
} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
} catch(e) {
this.empty().append( value );
}
} else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery( this );
self.html( value.call(this, i, self.html()) );
});
} else {
this.empty().append( value );
}
return this;
},
Argumentos Nomeados
Outras linguagens (como Python) permitem que se passe argumentos nomeados como um meio de passar apenas alguns argumentos e tornar os argumentos independentes da ordem em que são passados. Javascript não suporta diretamente o recurso de argumentos nomeados. Um padrão de design comumente usado em seu lugar é passar um mapa de propriedades / valores. Isso pode ser feito passando um objeto com propriedades e valores ou no ES6 e acima, você pode realmente passar um objeto Mapa em si.
Aqui está um exemplo ES5 simples:
jQuery's $.ajax() aceita uma forma de uso em que você apenas passa um único parâmetro que é um objeto Javascript regular com propriedades e valores. Quais propriedades você passa determinam quais argumentos / opções estão sendo passados para a chamada ajax. Alguns podem ser obrigatórios, muitos são opcionais. Como são propriedades de um objeto, não existe uma ordem específica. Na verdade, existem mais de 30 propriedades diferentes que podem ser passadas para aquele objeto, apenas uma (o url) é necessária.
Aqui está um exemplo:
$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
// process result here
});
Dentro da $.ajax()implementação, ele pode apenas interrogar quais propriedades foram passadas no objeto de entrada e usá-las como argumentos nomeados. Isso pode ser feito com for (prop in obj)ou obtendo todas as propriedades em uma matriz Object.keys(obj)e, em seguida, iterando essa matriz.
Esta técnica é usada muito comumente em Javascript quando há um grande número de argumentos e / ou muitos argumentos são opcionais. Observação: isso coloca um ônus sobre a função de implementação para garantir que um conjunto mínimo válido de argumentos esteja presente e dar ao chamador algum feedback de depuração o que está faltando se argumentos insuficientes forem passados (provavelmente lançando uma exceção com uma mensagem de erro útil) .
Em um ambiente ES6, é possível usar a desestruturação para criar propriedades / valores padrão para o objeto passado acima. Isso é discutido com mais detalhes neste artigo de referência .
Aqui está um exemplo desse artigo:
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
};
Isto cria propriedades padrão e os valores para a start, ende stepas propriedades de um objecto transmitido para a selectEntries()função.
Valores padrão para argumentos de função
No ES6, o Javascript adiciona suporte de idioma integrado para valores padrão de argumentos.
Por exemplo:
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
Uma descrição mais detalhada das maneiras como isso pode ser usado aqui no MDN .