Sobrecarga de função em Javascript - Melhores práticas

783

Qual é a melhor maneira de falsificar a sobrecarga de funções em Javascript?

Eu sei que não é possível sobrecarregar funções em Javascript como em outros idiomas. Se eu precisasse de uma função com dois usos foo(x)e foo(x,y,z)qual a melhor / preferida:

  1. Usando nomes diferentes em primeiro lugar
  2. Usando argumentos opcionais como y = y || 'default'
  3. Usando número de argumentos
  4. Verificando tipos de argumentos
  5. Ou como?
hamdiakoguz
fonte
14
Talvez seja útil perguntar por que você acha que precisa sobrecarregar as funções para começar. Eu acho que isso nos aproximará de uma solução real.
Breton
1
Isso está fechado, mas eu faço o seguinte: this.selectBy = {instance: selectByInstance, // Texto da função: selectByText, // Valor da função: selectByValue // Function};
Prisioneiro ZERO
Minha resposta mostra como executar a sobrecarga da função de tempo de execução, ela tem uma penalidade de velocidade e eu não recomendaria fazê-lo para contornar as especificações do Javascript. A sobrecarga de funções é realmente uma tarefa em tempo de compilação; eu apenas forneço a resposta para fins acadêmicos e deixo a seu critério decidir se deve ou não empregá-la no código.
Keldon Alleyne
2
Caso seja útil, eu construí uma estrutura js leve que permite a sobrecarga de método baseada em tipo. Obviamente, as mesmas advertências se aplicam ao desempenho, mas até agora funcionou bem para minhas necessidades e ainda tem muito espaço para melhorias: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Respostas:

602

A melhor maneira de sobrecarregar a função com parâmetros não é verificar o comprimento do argumento ou os tipos; verificar os tipos apenas tornará seu código lento e você se divertirá com matrizes, nulos, objetos etc.

O que a maioria dos desenvolvedores faz é aderir a um objeto como o último argumento para seus métodos. Este objeto pode conter qualquer coisa.

function foo(a, b, opts) {
  // ...
  if (opts['test']) { } //if test param exists, do something.. 
}


foo(1, 2, {"method":"add"});
foo(3, 4, {"test":"equals", "bar":"tree"});

Então você pode lidar com isso da maneira que quiser no seu método. [Switch, if-else, etc.]

epascarello
fonte
43
Você poderia fornecer uma implementação de exemplo de foo () que ilustra como esses parâmetros "opts" são usados ​​/ referenciados?
Moe Howard
24
Moe // Poderia ser assim; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard
80
Isso não é sobrecarga de função. A sobrecarga de funções está tendo duas funções separadas com o mesmo nome, mas com parâmetros diferentes. O que você está descrevendo é apenas uma função com um argumento de objeto no final.
D512 05/03
66
@ user1334007 é impossível sobrecarregar a função como você faria em Java / .NET. Sim, isso não é "exatamente" sobrecarregar, mas faz o trabalho.
precisa saber é o seguinte
18
Estou surpreso que ninguém já tenha perguntado isso: por que a verificação arguments.lengthnão é recomendada? Além disso, eu já estive aqui antes e li O que a maioria dos desenvolvedores faz é ... , mas tenho certeza de que este é o único lugar em que eu já vi isso. Esse método também estraga a açucaridade sintática de ter 'sobrecargas'!
C24w
169

Costumo fazer isso:

C #:

public string CatStrings(string p1)                  {return p1;}
public string CatStrings(string p1, int p2)          {return p1+p2.ToString();}
public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}

CatStrings("one");        // result = one
CatStrings("one",2);      // result = one2
CatStrings("one",2,true); // result = one2true

Equivalente a JavaScript:

function CatStrings(p1, p2, p3)
{
  var s = p1;
  if(typeof p2 !== "undefined") {s += p2;}
  if(typeof p3 !== "undefined") {s += p3;}
  return s;
};

CatStrings("one");        // result = one
CatStrings("one",2);      // result = one2
CatStrings("one",2,true); // result = one2true

Este exemplo em particular é realmente mais elegante em javascript que em c #. Os parâmetros não especificados são 'indefinidos' em javascript, que são avaliados como falsos em uma instrução if. No entanto, a definição da função não transmite a informação de que p2 e p3 são opcionais. Se você precisar de muita sobrecarga, o jQuery decidiu usar um objeto como parâmetro, por exemplo, jQuery.ajax (opções). Concordo com eles que essa é a abordagem mais poderosa e claramente documentável para sobrecarga, mas raramente preciso de mais de um ou dois parâmetros opcionais rápidos.

EDIT: teste IF alterado por sugestão de Ian

rocketsarefast
fonte
16
Parâmetros que não são especificados estão undefinedem JS, não null. Como prática recomendada, você nunca deve definir nada undefined, portanto, isso não deve ser um problema, desde que você altere seu teste para p2 === undefined.
Tamzin Blake
3
Se você passar falsecomo o último argumento, ele não concatenará "false"até o final, porque o if(p3)não será ramificado.
dreamlax
5
Apenas uma observação rápida, você typeof p2 === "undefined"provavelmente é o inverso do que você está esperando na instância do seu exemplo, eu acho typeof p2 !== "undefined"que é o que você pretendia. Além disso, posso sugerir sendo seu suposto corda concatenate, número e booleano que você realmente fazp2 === "number"; p3 === "boolean"
WillFM
8
Eu gosto de fazer isso: p3 = p3 || 'valor padrão';
Dorian
3
Qual é o significado de ===e !==? Por que não apenas usar ==e !=?
Ricardo Cruz
76

Não há nenhuma função real sobrecarregando no JavaScript, pois ele permite passar qualquer número de parâmetros de qualquer tipo. Você deve verificar dentro da função quantos argumentos foram passados ​​e que tipo eles são.

quiabo
fonte
1
John Resig (da jQuery) tentou uma vez isso, mas a tentativa foi puramente acadêmica e não forneceu nenhum benefício real.
scunliffe
14
A função de John Resig sobrecarrega aqui ejohn.org/blog/javascript-method-overloading
Terrance
@ Terrance: Eu também gosto do método de Resig. Ele funciona como um encanto. Eu só preciso encontrar uma maneira de criar um teste para validar casos de uso.
chrisvillanueva
"Esta função não mudará o mundo, mas é curta, concisa e usa um recurso JavaScript obscuro - por isso ganha no meu livro." :-)
Frerich Raabe
68

A resposta correta é NÃO HÁ SOBRECARGA NO JAVASCRIPT.

A verificação / mudança dentro da função não está sobrecarregada.

O conceito de sobrecarga: Em algumas linguagens de programação, sobrecarga de função ou sobrecarga de método é a capacidade de criar vários métodos com o mesmo nome com diferentes implementações. As chamadas para uma função sobrecarregada executarão uma implementação específica dessa função apropriada ao contexto da chamada, permitindo que uma chamada de função execute tarefas diferentes, dependendo do contexto.

Por exemplo, doTask () e doTask (objeto O) são métodos sobrecarregados. Para chamar o último, um objeto deve ser passado como parâmetro, enquanto o primeiro não requer um parâmetro e é chamado com um campo de parâmetro vazio. Um erro comum seria atribuir um valor padrão ao objeto no segundo método, o que resultaria em um erro de chamada ambíguo, pois o compilador não saberia qual dos dois métodos usar.

https://en.wikipedia.org/wiki/Function_overloading

Todas as implementações sugeridas são ótimas, mas, verdade seja dita, não há implementação nativa para JavaScript.

Marco
fonte
4
finalmente uma resposta normal! NÃO HÁ SOBRECARGA NO JAVASCRIPT.
Razvan Tudorica 19/11/19
4
O OP pediu uma maneira de fingir sobrecarga.
Ateur Games 13/12/19
Como eu disse antes, estamos aqui para educar as pessoas, não apenas damos respostas a elas sem verificar se o que elas estão pedindo está certo.
Marco
27

Há duas maneiras de abordar isso melhor:

  1. Passe um dicionário (array associativo) se quiser deixar muita flexibilidade

  2. Pegue um objeto como argumento e use herança baseada em protótipo para adicionar flexibilidade.

t3rse
fonte
1
este foi o meu pensamento inicial, no entanto, se a função que você está criando é para ser usado em uma biblioteca ou por outros, enumerando os valores claramente pode ser útil
roberthuttinger
19

Aqui está uma abordagem que permite a sobrecarga real de métodos usando tipos de parâmetros, mostrados abaixo:

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);

Edit (2018) : desde que foi escrito em 2011, a velocidade das chamadas diretas ao método aumentou bastante, enquanto a velocidade dos métodos sobrecarregados não.

Não é uma abordagem que eu recomendaria, mas é um exercício de reflexão que vale a pena pensar em como você pode resolver esses tipos de problemas.


Aqui está uma referência das diferentes abordagens - https://jsperf.com/function-overloading . Ele mostra que a sobrecarga de funções (levando em consideração os tipos) pode ser cerca de 13 vezes mais lenta no V8 do Google Chrome a partir de 16.0 (beta) .

Além de passar um objeto (ie {x: 0, y: 0}), também é possível adotar a abordagem C quando apropriado, nomeando os métodos de acordo. Por exemplo, Vector.AddVector (vector), Vector.AddIntegers (x, y, z, ...) e Vector.AddArray (integerArray). Você pode olhar para as bibliotecas C, como o OpenGL, para inspirar nomes.

Editar : eu adicionei uma referência para passar um objeto e testá-lo usando ambos 'param' in arge arg.hasOwnProperty('param'), e a sobrecarga de função é muito mais rápida do que passar um objeto e verificar propriedades (pelo menos nesta referência).

Do ponto de vista do projeto, a sobrecarga de função é válida ou lógica apenas se os parâmetros sobrecarregados corresponderem à mesma ação. Portanto, é lógico que deve haver um método subjacente que se preocupe apenas com detalhes específicos, caso contrário isso pode indicar escolhas inadequadas de design. Portanto, também se pode resolver o uso de sobrecarga de função convertendo dados em um objeto respectivo. É claro que é preciso considerar o escopo do problema, pois não há necessidade de criar designs elaborados se a sua intenção é apenas imprimir um nome, mas para o design de estruturas e bibliotecas esse pensamento é justificado.

Meu exemplo vem de uma implementação de retângulo - daí a menção de dimensão e ponto. Talvez Retângulo poderia adicionar um GetRectangle()método para o Dimensione Pointprotótipo, e, em seguida, a função de sobrecarga questão está classificada. E quanto aos primitivos? Bem, temos o comprimento do argumento, que agora é um teste válido, já que os objetos têm um GetRectangle()método.

function Dimension() {}
function Point() {}

var Util = {};

Util.Redirect = function (args, func) {
  'use strict';
  var REDIRECT_ARGUMENT_COUNT = 2;

  if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
    return null;
  }

  for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
    var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
    var currentArgument = args[argsIndex];
    var currentType = arguments[i];
    if(typeof(currentType) === 'object') {
      currentType = currentType.constructor;
    }
    if(typeof(currentType) === 'number') {
      currentType = 'number';
    }
    if(typeof(currentType) === 'string' && currentType === '') {
      currentType = 'string';
    }
    if(typeof(currentType) === 'function') {
      if(!(currentArgument instanceof currentType)) {
        return null;
      }
    } else {
      if(typeof(currentArgument) !== currentType) {
        return null;
      }
    } 
  }
  return [func.apply(this, args)];
}

function FuncPoint(point) {}
function FuncDimension(dimension) {}
function FuncDimensionPoint(dimension, point) {}
function FuncXYWidthHeight(x, y, width, height) { }

function Func() {
  Util.Redirect(arguments, FuncPoint, Point);
  Util.Redirect(arguments, FuncDimension, Dimension);
  Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
  Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
}

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Keldon Alleyne
fonte
16

A melhor maneira realmente depende da função e dos argumentos. Cada uma das suas opções é uma boa ideia em diferentes situações. Geralmente, eu tento isso na seguinte ordem até que um deles funcione:

  1. Usando argumentos opcionais como y = y || 'padrão'. Isso é conveniente se você pode fazê-lo, mas nem sempre funciona na prática, por exemplo, quando 0 / nulo / indefinido seria um argumento válido.

  2. Usando número de argumentos. Semelhante à última opção, mas pode funcionar quando o número 1 não funcionar.

  3. Verificando tipos de argumentos. Isso pode funcionar em alguns casos em que o número de argumentos é o mesmo. Se você não pode determinar com segurança os tipos, pode ser necessário usar nomes diferentes.

  4. Usando nomes diferentes em primeiro lugar. Pode ser necessário fazer isso se as outras opções não funcionarem, não forem práticas ou para consistência com outras funções relacionadas.

Matthew Crumley
fonte
14

Se eu precisasse de uma função com dois usos foo (x) e foo (x, y, z), qual é a melhor / preferida?

O problema é que o JavaScript NÃO suporta nativamente a sobrecarga de métodos. Portanto, se ele vê / analisa duas ou mais funções com o mesmo nome, apenas considera a última função definida e sobrescreve as anteriores.

Uma das maneiras que considero adequadas para a maioria dos casos é a seguinte -

Vamos dizer que você tem método

function foo(x)
{
} 

Em vez de sobrecarregar o método que não é possível em javascript, você pode definir um novo método

fooNew(x,y,z)
{
}

e modifique a 1ª função da seguinte maneira -

function foo(arguments)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

Se você tiver muitos desses métodos sobrecarregados, considere usar switchapenas if-elsedeclarações.

( mais detalhes )

PS: O link acima vai para o meu blog pessoal, com detalhes adicionais.

Aniket Thakur
fonte
9

Não tenho certeza sobre as melhores práticas, mas eis como faço:

/*
 * Object Constructor
 */
var foo = function(x) {
    this.x = x;
};

/*
 * Object Protoype
 */
foo.prototype = {
    /*
     * f is the name that is going to be used to call the various overloaded versions
     */
    f: function() {

        /*
         * Save 'this' in order to use it inside the overloaded functions
         * because there 'this' has a different meaning.
         */   
        var that = this;  

        /* 
         * Define three overloaded functions
         */
        var f1 = function(arg1) {
            console.log("f1 called with " + arg1);
            return arg1 + that.x;
        }

        var f2 = function(arg1, arg2) {
             console.log("f2 called with " + arg1 + " and " + arg2);
             return arg1 + arg2 + that.x;
        }

        var f3 = function(arg1) {
             console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
             return arg1[0] + arg1[1];
        }

        /*
         * Use the arguments array-like object to decide which function to execute when calling f(...)
         */
        if (arguments.length === 1 && !Array.isArray(arguments[0])) {
            return f1(arguments[0]);
        } else if (arguments.length === 2) {
            return f2(arguments[0], arguments[1]);
        } else if (arguments.length === 1 && Array.isArray(arguments[0])) {
            return f3(arguments[0]);
        }
    } 
}

/* 
 * Instantiate an object
 */
var obj = new foo("z");

/*
 * Call the overloaded functions using f(...)
 */
console.log(obj.f("x"));         // executes f1, returns "xz"
console.log(obj.f("x", "y"));    // executes f2, returns "xyz"
console.log(obj.f(["x", "y"]));  // executes f3, returns "xy"
chessweb
fonte
2
@Luis: Adicionei alguns comentários úteis.
chessweb
6

Eu apenas tentei isso, talvez atenda às suas necessidades. Dependendo do número dos argumentos, você pode acessar uma função diferente. Você o inicializa na primeira vez que o chama. E o mapa de funções está oculto no fechamento.

TEST = {};

TEST.multiFn = function(){
    // function map for our overloads
    var fnMap = {};

    fnMap[0] = function(){
        console.log("nothing here");
        return this;    //    support chaining
    }

    fnMap[1] = function(arg1){
        //    CODE here...
        console.log("1 arg: "+arg1);
        return this;
    };

    fnMap[2] = function(arg1, arg2){
        //    CODE here...
        console.log("2 args: "+arg1+", "+arg2);
        return this;
    };

    fnMap[3] = function(arg1,arg2,arg3){
        //    CODE here...
        console.log("3 args: "+arg1+", "+arg2+", "+arg3);
        return this;
    };

    console.log("multiFn is now initialized");

    //    redefine the function using the fnMap in the closure
    this.multiFn = function(){
        fnMap[arguments.length].apply(this, arguments);
        return this;
    };

    //    call the function since this code will only run once
    this.multiFn.apply(this, arguments);

    return this;    
};

Teste-o.

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");
AntouanK
fonte
5

Como o JavaScript não possui opções de sobrecarga de função, o objeto pode ser usado. Se houver um ou dois argumentos obrigatórios, é melhor mantê-los separados do objeto de opções. Aqui está um exemplo de como usar o objeto de opções e os valores preenchidos para o valor padrão, caso o valor não tenha sido passado no objeto de opções.

    function optionsObjectTest(x, y, opts) {
        opts = opts || {}; // default to an empty options object

        var stringValue = opts.stringValue || "string default value";
        var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
        var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;

        return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";

}

Aqui está um exemplo de como usar o objeto de opções

Vlad Bezden
fonte
4

Não há como funcionar sobrecarregando em javascript. Portanto, recomendo o seguinte pelo typeof()método, em vez de múltiplas funções, para a sobrecarga falsa.

function multiTypeFunc(param)
{
    if(typeof param == 'string') {
        alert("I got a string type parameter!!");
     }else if(typeof param == 'number') {
        alert("I got a number type parameter!!");
     }else if(typeof param == 'boolean') {
        alert("I got a boolean type parameter!!");
     }else if(typeof param == 'object') {
        alert("I got a object type parameter!!");
     }else{
        alert("error : the parameter is undefined or null!!");
     }
}

Boa sorte!

Smith Lee
fonte
24
Pelo amor de Deus! Use uma instrução switch!
jedmao
8
Além disso, se você insistir em não usar um comutador, chame apenas typeof uma vez. var type = typeof param; if (type === 'string') ...
Walter Stabosz
+1 para comentar o "===". A outra vantagem da instrução switch sobre if (... == ...) é a segurança do tipo.
Nathan Cooper
4

INTRODUÇÃO

Até agora, ler tantas respostas daria dor de cabeça a qualquer um. Qualquer pessoa que tente conhecer o conceito precisará conhecer os seguintes pré-requisitos .

Function overloading Definition, Function Length property,Function argument property

Function overloadingem sua forma mais simples, significa que uma função executa tarefas diferentes com base no número de argumentos que estão sendo transmitidos para ela. Notavelmente, a TASK1, TASK2 e TASK3 estão destacadas abaixo e estão sendo executadas com base no número de argumentspassadas para a mesma função fooYo.

// if we have a function defined below
function fooYo(){
     // do something here
}
// on invoking fooYo with different number of arguments it should be capable to do different things

fooYo();  // does TASK1
fooYo('sagar'); // does TASK2
fooYo('sagar','munjal'); // does TAKS3

OBSERVAÇÃO - JS não fornece capacidade embutida de sobrecarga de função.

Alternativa

John E Resig (criador do JS) apontou uma alternativa que usa os pré-requisitos acima para obter a capacidade de implementar a sobrecarga de funções.

O código abaixo usa uma abordagem simples, mas ingênua, usando if-elseou switchinstrução

  • avalia a argument-lengthpropriedade.
  • valores diferentes resultam na invocação de funções diferentes.

var ninja = {
  whatever: function() {
       switch (arguments.length) {
         case 0:
           /* do something */
           break;
         case 1:
           /* do something else */
           break;
         case 2:
           /* do yet something else */
           break;
       //and so on ...
    } 
  }
}

Outra técnica é muito mais limpa e dinâmica. O destaque dessa técnica é a addMethodfunção genérica.

  • definimos uma função addMethodque é usada para adicionar funções diferentes a um objeto com o mesmo nome, mas com funcionalidades diferentes .

  • abaixo, a addMethodfunção aceita três parâmetros de nome de objeto object, nome nameda função e a função que queremos que seja chamada fn.

  • A addMethoddefinição interna var oldarmazena a referência à anterior functionsendo armazenada com a ajuda do fechamento - uma bolha protetora.

function addMethod(object, name, fn) {
  var old = object[name];
  object[name] = function(){
    if (fn.length == arguments.length)
      return fn.apply(this, arguments)
    else if (typeof old == 'function')
      return old.apply(this, arguments);
  };
};

  • use o depurador para entender o fluxo de código.
  • abaixo, addMethodadiciona três funções que, quando invocadas usando ninja.whatever(x)o número de argumentos xque podem ser qualquer coisa, ou seja, em branco ou uma ou mais de uma, invocam funções diferentes, conforme definido ao usar a addMethodfunção.

var ninja = {};
debugger;


addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });
addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });
addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });


ninja.whatever();
ninja.whatever(1,2);
ninja.whatever(3);

Sagar Munjal
fonte
4

Outra maneira de abordar isso é usando a variável especial: argumentos , esta é uma implementação:

function sum() {
    var x = 0;
    for (var i = 0; i < arguments.length; ++i) {
        x += arguments[i];
    }
    return x;
}

para que você possa modificar esse código para:

function sum(){
    var s = 0;
    if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
    return s;
}
Bashir Abdelwahed
fonte
3

Veja isso. É muito legal http://ejohn.org/blog/javascript-method-overloading/ Truque o Javascript para permitir que você faça chamadas assim:

var users = new Users();
users.find(); // Finds all
users.find("John"); // Finds users by name
users.find("John", "Resig"); // Finds users by first and last name
Jaider
fonte
Olá Jaider, confira minha resposta, ela contém código para sobrecarregar o método javascript real . Estou falando Func(new Point())e Func(new Rectangle())executarei funções diferentes. Mas devo salientar que este é um hack sujo, pois a sobrecarga de método é realmente uma tarefa em tempo de compilação, não em tempo de execução.
Keldon Alleyne
3

Função sobrecarga em Javascript:

Sobrecarga de função é a capacidade de uma linguagem de programação criar várias funções com o mesmo nome com diferentes implementações. quando uma função sobrecarregada é chamada, ela executará uma implementação específica dessa função, apropriada ao contexto da chamada. Esse contexto geralmente é a quantidade de argumentos recebidos e permite que uma chamada de função se comporte de maneira diferente, dependendo do contexto.

Javascript não possui sobrecarga de função embutida. No entanto, esse comportamento pode ser emulado de várias maneiras. Aqui está um simples e conveniente:

function sayHi(a, b) {
  console.log('hi there ' + a);
  if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
}

sayHi('Frank', 'Willem');

Em cenários em que você não sabe quantos argumentos estará recebendo, pode usar o operador resto, que é de três pontos... . Ele converterá o restante dos argumentos em uma matriz. Cuidado com a compatibilidade do navegador. Aqui está um exemplo:

function foo (a, ...b) {
  console.log(b);
}

foo(1,2,3,4);
foo(1,2);

Willem van der Veen
fonte
2

Como este post já contém muitas soluções diferentes, pensei em publicar outro.

function onlyUnique(value, index, self) {
    return self.indexOf(value) === index;
}

function overload() {
   var functions = arguments;
   var nroffunctionsarguments = [arguments.length];
    for (var i = 0; i < arguments.length; i++) {
        nroffunctionsarguments[i] = arguments[i].length;
    }
    var unique = nroffunctionsarguments.filter(onlyUnique);
    if (unique.length === arguments.length) {
        return function () {
            var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
            return functions[indexoffunction].apply(this, arguments);
        }
    }
    else throw new TypeError("There are multiple functions with the same number of parameters");

}

isso pode ser usado como mostrado abaixo:

var createVector = overload(
        function (length) {
            return { x: length / 1.414, y: length / 1.414 };
        },
        function (a, b) {
            return { x: a, y: b };
        },
        function (a, b,c) {
            return { x: a, y: b, z:c};
        }
    );
console.log(createVector(3, 4));
console.log(createVector(3, 4,5));
console.log(createVector(7.07));

Esta solução não é perfeita, mas quero apenas demonstrar como isso pode ser feito.

remyH
fonte
2

Você pode usar o 'addMethod' de John Resig. Com esse método, você pode "sobrecarregar" os métodos com base na contagem de argumentos.

// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
    var old = object[ name ];
    object[ name ] = function(){
        if ( fn.length == arguments.length )
            return fn.apply( this, arguments );
        else if ( typeof old == 'function' )
            return old.apply( this, arguments );
    };
}

Eu também criei uma alternativa para esse método que usa o cache para armazenar as variações da função. As diferenças são descritas aqui

// addMethod - By Stavros Ioannidis
function addMethod(obj, name, fn) {
  obj[name] = obj[name] || function() {
    // get the cached method with arguments.length arguments
    var method = obj[name].cache[arguments.length];

    // if method exists call it 
    if ( !! method)
      return method.apply(this, arguments);
    else throw new Error("Wrong number of arguments");
  };

  // initialize obj[name].cache
  obj[name].cache = obj[name].cache || {};

  // Check if a method with the same number of arguments exists  
  if ( !! obj[name].cache[fn.length])
    throw new Error("Cannot define multiple '" + name +
      "' methods with the same number of arguments!");

  // cache the method with fn.length arguments
  obj[name].cache[fn.length] = function() {
    return fn.apply(this, arguments);
  };
}
istavros
fonte
2

Padrão de encaminhamento => a melhor prática para sobrecarga de JS

Encaminhar para outra função cujo nome é construído a partir dos 3º e 4º pontos:

  1. Usando número de argumentos
  2. Verificando tipos de argumentos
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

Aplicação no seu caso:

 function foo(){
          return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);

  }
   //------Assuming that `x` , `y` and `z` are String when calling `foo` . 

  /**-- for :  foo(x)*/
  function foo_1_string(){
  }
  /**-- for : foo(x,y,z) ---*/
  function foo_3_string_string_string(){

  }

Outra amostra complexa:

      function foo(){
          return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }

        /** one argument & this argument is string */
      function foo_1_string(){

      }
       //------------
       /** one argument & this argument is object */
      function foo_1_object(){

      }
      //----------
      /** two arguments & those arguments are both string */
      function foo_2_string_string(){

      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function foo_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }

       //--- And so on ....   
Abdennour TOUMI
fonte
2

Função Sobrecarga via Polimorfismo Dinâmico em 100 linhas de JS

Esta é a partir de um conjunto maior de código que inclui os isFn, isArretc. funções de controlo do tipo. A versão do VanillaJS abaixo foi reformulada para remover todas as dependências externas; no entanto, você terá que definir suas próprias funções de verificação de tipo para uso nas .add()chamadas.

Nota: Esta é uma função de execução automática (para que possamos ter um escopo de fechamento / fechamento), daí a atribuição para, em window.overloadvez de function overload() {...}.

window.overload = function () {
    "use strict"

    var a_fnOverloads = [],
        _Object_prototype_toString = Object.prototype.toString
    ;

    function isFn(f) {
        return (_Object_prototype_toString.call(f) === '[object Function]');
    } //# isFn

    function isObj(o) {
        return !!(o && o === Object(o));
    } //# isObj

    function isArr(a) {
        return (_Object_prototype_toString.call(a) === '[object Array]');
    } //# isArr

    function mkArr(a) {
        return Array.prototype.slice.call(a);
    } //# mkArr

    function fnCall(fn, vContext, vArguments) {
        //# <ES5 Support for array-like objects
        //#     See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
        vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));

        if (isFn(fn)) {
            return fn.apply(vContext || this, vArguments);
        }
    } //# fnCall

    //# 
    function registerAlias(fnOverload, fn, sAlias) {
        //# 
        if (sAlias && !fnOverload[sAlias]) {
            fnOverload[sAlias] = fn;
        }
    } //# registerAlias

    //# 
    function overload(vOptions) {
        var oData = (isFn(vOptions) ?
                { default: vOptions } :
                (isObj(vOptions) ?
                    vOptions :
                    {
                        default: function (/*arguments*/) {
                            throw "Overload not found for arguments: [" + mkArr(arguments) + "]";
                        }
                    }
                )
            ),
            fnOverload = function (/*arguments*/) {
                var oEntry, i, j,
                    a = arguments,
                    oArgumentTests = oData[a.length] || []
                ;

                //# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
                for (i = 0; i < oArgumentTests.length; i++) {
                    oEntry = oArgumentTests[i];

                    //# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
                    for (j = 0; j < a.length; j++) {
                        if (!oArgumentTests[i].tests[j](a[j])) {
                            oEntry = undefined;
                            break;
                        }
                    }

                    //# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
                    if (oEntry) {
                        break;
                    }
                }

                //# If we found our oEntry above, .fn.call its .fn
                if (oEntry) {
                    oEntry.calls++;
                    return fnCall(oEntry.fn, this, a);
                }
                //# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
                else {
                    return fnCall(oData.default, this, a);
                }
            } //# fnOverload
        ;

        //# 
        fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
            var i,
                bValid = isFn(fn),
                iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
            ;

            //# 
            if (bValid) {
                //# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
                for (i = 0; i < iLen; i++) {
                    if (!isFn(a_vArgumentTests[i])) {
                        bValid = _false;
                    }
                }
            }

            //# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
            if (bValid) {
                oData[iLen] = oData[iLen] || [];
                oData[iLen].push({
                    fn: fn,
                    tests: a_vArgumentTests,
                    calls: 0
                });

                //# 
                registerAlias(fnOverload, fn, sAlias);

                return fnOverload;
            }
            //# Else one of the passed arguments was not bValid, so throw the error
            else {
                throw "poly.overload: All tests must be functions or strings referencing `is.*`.";
            }
        }; //# overload*.add

        //# 
        fnOverload.list = function (iArgumentCount) {
            return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
        }; //# overload*.list

        //# 
        a_fnOverloads.push(fnOverload);
        registerAlias(fnOverload, oData.default, "default");

        return fnOverload;
    } //# overload

    //# 
    overload.is = function (fnTarget) {
        return (a_fnOverloads.indexOf(fnTarget) > -1);
    } //# overload.is

    return overload;
}();

Uso:

O chamador define suas funções sobrecarregadas atribuindo uma variável ao retorno de overload(). Graças ao encadeamento, as sobrecargas adicionais podem ser definidas em série:

var myOverloadedFn = overload(function(){ console.log("default", arguments) })
    .add(function(){ console.log("noArgs", arguments) }, [], "noArgs")
    .add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str")
;

O argumento opcional único para overload()definir a função "padrão" a ser chamada se a assinatura não puder ser identificada. Os argumentos para .add()são:

  1. fn: functiondefinindo a sobrecarga;
  2. a_vArgumentTests: Arrayde functions definindo os testes a serem executados no arguments. Cada functionum aceita um único argumento e retorna truethy com base em se o argumento é válido;
  3. sAlias(Opcional): stringdefinir o alias para acessar diretamente a função de sobrecarga ( fn), por exemplo myOverloadedFn.noArgs(), chamará essa função diretamente, evitando os testes de polimorfismo dinâmico dos argumentos.

Essa implementação, na verdade, permite mais do que apenas sobrecargas de funções tradicionais, pois o segundo a_vArgumentTestsargumento .add()na prática define tipos personalizados. Portanto, você pode bloquear argumentos não apenas com base no tipo, mas também em intervalos, valores ou coleções de valores!

Se você procurar nas 145 linhas de código, overload()verá que cada assinatura é categorizada pelo número de argumentspassadas para ela. Isso é feito para limitar o número de testes que estamos executando. Também acompanho uma contagem de chamadas. Com algum código adicional, as matrizes de funções sobrecarregadas podem ser re-classificadas para que as funções chamadas mais comuns sejam testadas primeiro, adicionando novamente alguma medida de aprimoramento de desempenho.

Agora, existem algumas advertências ... Como o Javascript é pouco digitado, você deve ter cuidado com o vArgumentTestsque integerpode ser validado como umfloat , etc.

Versão JSCompress.com (1114 bytes, 744 bytes compactados em g):

window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();
Campbeln
fonte
2

Agora você pode sobrecarregar as funções no ECMAScript 2018 sem polyfills, verificando o comprimento / tipo do var, etc., basta usar a sintaxe de dispersão .

function foo(var1, var2, opts){
  // set default values for parameters
  const defaultOpts = {
    a: [1,2,3],
    b: true,
    c: 0.3289,
    d: "str",
  }
  // merge default and passed-in parameters
  // defaultOpts must go first!
  const mergedOpts = {...defaultOpts, ...opts};

  // you can now refer to parameters like b as mergedOpts.b,
  // or just assign mergedOpts.b to b
  console.log(mergedOpts.a);
  console.log(mergedOpts.b);
  console.log(mergedOpts.c);  
  console.log(mergedOpts.d);
}
// the parameters you passed in override the default ones
// all JS types are supported: primitives, objects, arrays, functions, etc.
let var1, var2="random var";
foo(var1, var2, {a: [1,2], d: "differentString"});

// parameter values inside foo:
//a: [1,2]
//b: true
//c: 0.3289
//d: "differentString"

O que é sintaxe de propagação?

A proposta Rest / Spread Properties for ECMAScript (estágio 4) adiciona propriedades de spread a literais de objetos. Ele copia propriedades enumeráveis ​​próprias de um objeto fornecido para um novo objeto. Mais sobre MDDN

Nota: a sintaxe de propagação em literais de objetos não funciona no Edge e no IE e é um recurso experimental. veja compatibilidade do navegador

AlienKevin
fonte
2

Algo assim pode ser feito para sobrecarga de funções.

function addCSS(el, prop, val) {
  return {
    2: function() {
      // when two arguments are set
      // now prop is an oject
      for (var i in prop) {
          el.style[i] = prop[i];
      }
    },
    3: function() {
      // when three arguments are set
      el.style[prop] = val;
    }
    }[arguments.length]();
}
// usage
var el = document.getElementById("demo");
addCSS(el, "color", "blue");
addCSS(el, {
    "backgroundColor": "black",
  "padding": "10px"
});

Fonte

Supun Kavinda
fonte
1

Esta é uma pergunta antiga, mas acho que precisa de outra entrada (embora eu duvide que alguém a leia). O uso de Expressões de Função Imediatamente Invocadas (IIFE) pode ser usado em conjunto com fechamentos e funções em linha para permitir a sobrecarga de funções. Considere o seguinte exemplo (artificial):

var foo;

// original 'foo' definition
foo = function(a) {
  console.log("a: " + a);
}

// define 'foo' to accept two arguments
foo = (function() {
  // store a reference to the previous definition of 'foo'
  var old = foo;

  // use inline function so that you can refer to it internally
  return function newFoo(a,b) {

    // check that the arguments.length == the number of arguments 
    // defined for 'newFoo'
    if (arguments.length == newFoo.length) {
      console.log("a: " + a);
      console.log("b: " + b);

    // else if 'old' is a function, apply it to the arguments
    } else if (({}).toString.call(old) === '[object Function]') {
      old.apply(null, arguments);
    }
  }
})();

foo(1);
> a: 1
foo(1,2);
> a: 1
> b: 2
foo(1,2,3)
> a: 1

Em resumo, o uso do IIFE cria um escopo local, permitindo definir a variável privada oldpara armazenar uma referência à definição inicial da função foo. Essa função então retorna uma função embutida newFooque registra o conteúdo dos dois argumentos se for passado exatamente dois argumentos ae bou chama a oldfunção if arguments.length !== 2. Esse padrão pode ser repetido várias vezes para dotar uma variável de várias defesas funcionais diferentes.

wvandaal
fonte
1

Eu gostaria de compartilhar um exemplo útil de abordagem do tipo sobrecarregado.

function Clear(control)
{
  var o = typeof control !== "undefined" ? control : document.body;
  var children = o.childNodes;
  while (o.childNodes.length > 0)
    o.removeChild(o.firstChild);
}

Uso: Clear (); // Limpa todo o documento

Limpar (myDiv); // Limpa o painel referenciado por myDiv

Alexander Chernosvitov
fonte
1

O JavaScript é uma linguagem sem tipo e acho que faz sentido sobrecarregar um método / função com relação ao número de parâmetros. Portanto, eu recomendaria verificar se o parâmetro foi definido:

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};
Tony
fonte
1
Só quero observar que não digitado não significa "nenhum tipo".
hamdiakoguz
1

Em julho de 2017, a seguinte técnica era comum. Observe que também podemos executar a verificação de tipo dentro da função.

function f(...rest){   // rest is an array
   console.log(rest.length);
   for (v of rest) if (typeof(v)=="number")console.log(v);
}
f(1,2,3);  // 3 1 2 3
Chong Lip Phang
fonte
1

Para o seu caso de uso, é assim que eu o abordaria ES6(já que já é o final de 2017):

const foo = (x, y, z) => {
  if (y && z) {
    // Do your foo(x, y, z); functionality
    return output;
  }
  // Do your foo(x); functionality
  return output;
}

Obviamente, você pode adaptar isso para trabalhar com qualquer quantidade de parâmetros e apenas alterar suas instruções condicionais de acordo.

Barry Michael Doyle
fonte
1

não há sobrecarga real no JS, de qualquer forma, ainda podemos simular a sobrecarga do método de várias maneiras:

método # 1: usar objeto

function test(x,options){
  if("a" in options)doSomething();
  else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});

método # 2: use os parâmetros rest (spread)

function test(x,...p){
 if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")
else if (p[1])console.log("2 params passed");
else console.log("1 param passed");
}

método # 3: use indefinido

function test(x, y, z){
 if(typeof(z)=="undefined")doSomething();
}

método # 4: verificação de tipo

function test(x){
 if(typeof(x)=="string")console.log("a string passed")
 else ...
}
xx aa
fonte
1

Embora os parâmetros padrão não estejam sobrecarregando, eles podem resolver alguns dos problemas enfrentados pelos desenvolvedores nesta área. A entrada é estritamente decidida pelo pedido, não é possível reordenar como desejar, como na sobrecarga clássica:

function transformer(
    firstNumber = 1,
    secondNumber = new Date().getFullYear(),
    transform = function multiply(firstNumber, secondNumber) {
        return firstNumber * secondNumber;
    }
) {
    return transform(firstNumber, secondNumber);
}

console.info(transformer());
console.info(transformer(8));
console.info(transformer(2, 6));
console.info(transformer(undefined, 65));

function add(firstNumber, secondNumber) {
    return firstNumber + secondNumber;
}
console.info(transformer(undefined, undefined, add));
console.info(transformer(3, undefined, add));

Resultados em (para o ano 2020):

2020
16160
12
65
2021
2023

Mais informações: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

Karl Morrison
fonte
0

A primeira opção realmente merece atenção porque é o que eu vim em uma configuração de código bastante complexa. Então, minha resposta é

  1. Usando nomes diferentes em primeiro lugar

Com uma pequena dica, porém essencial, os nomes devem parecer diferentes para o computador, mas não para você. Nomeie funções sobrecarregadas como: func, func1, func2.

alehro
fonte
Eu estava indo para tentar sobrecarregar, mas decidiu usar apenas nomes diferentes, por exemplo, getDeviceInfoByID e getDeviceInfoByType ...
CramerTV