Sobrecarga de funciones en Javascript - Mejores prácticas

784

¿Cuál es la mejor forma de falsificar la sobrecarga de funciones en Javascript?

Sé que no es posible sobrecargar funciones en Javascript como en otros idiomas. Si necesitaba una función con dos usos foo(x)y foo(x,y,z)cuál es la mejor / preferida forma:

  1. Usando diferentes nombres en primer lugar
  2. Usando argumentos opcionales como y = y || 'default'
  3. Usando el número de argumentos
  4. Comprobación de tipos de argumentos
  5. ¿O como?
hamdiakoguz
fuente
14
Quizás sería útil preguntar por qué cree que necesita sobrecargar la función para empezar. Creo que eso nos acercará a una solución real.
Breton
1
Esto está cerrado, pero hago lo siguiente: this.selectBy = {instancia: selectByInstance, // Texto de función: selectByText, // Valor de función: selectByValue // Function};
Prisionero CERO
Mi respuesta muestra cómo hacer una sobrecarga de la función de tiempo de ejecución, tiene una penalización de velocidad y no recomendaría hacerlo para evitar las especificaciones de Javascript. La sobrecarga de funciones es realmente una tarea de tiempo de compilación, solo proporciono la respuesta con fines académicos y la dejo a su propia discreción en cuanto a si emplearla o no en el código.
Keldon Alleyne
2
En caso de que sea útil, he creado un framework js liviano que permite la sobrecarga de métodos basados ​​en tipos. Obviamente, se aplican las mismas advertencias con respecto al rendimiento, pero hasta ahora ha funcionado bien para mis necesidades y todavía tiene mucho margen de mejora: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Respuestas:

602

La mejor manera de sobrecargar funciones con parámetros es no verificar la longitud del argumento o los tipos; verificar los tipos solo hará que su código sea más lento y se divertirá con matrices, nulos, objetos, etc.

Lo que la mayoría de los desarrolladores hacen es agregar un objeto como último argumento para sus métodos. Este objeto puede contener cualquier cosa.

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"});

Luego puede manejarlo de la forma que desee en su método. [Interruptor, si no, etc.]

epascarello
fuente
43
¿Podría proporcionar una implementación de muestra de foo () que ilustre cómo se usan / hacen referencia estos parámetros de "opts"?
Moe Howard
24
Moe // Podría ser así; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard el
80
Esto no es sobrecarga de funciones. La sobrecarga de funciones tiene dos funciones separadas con el mismo nombre pero parámetros diferentes. Lo que estás describiendo es solo una función con un argumento de objeto al final.
d512
66
@ user1334007 es imposible tener una sobrecarga de funciones como lo haría en Java / .NET. Sí, esto no es una sobrecarga "exactamente", pero hace el trabajo.
epascarello
18
Me sorprende que nadie haya preguntado esto ya: ¿por qué arguments.lengthno se recomienda la comprobación ? Además, he estado aquí antes y leí Lo que la mayoría de los desarrolladores hacen es ... , pero estoy seguro de que este es el único lugar que he visto hacer eso. ¡Ese método también estropea la azucaración sintáctica de tener 'sobrecargas'!
c24w
169

A menudo hago esto:

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

JavaScript equivalente:

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 ejemplo particular es en realidad más elegante en javascript que C #. Los parámetros que no se especifican son 'indefinidos' en javascript, que se evalúa como falso en una declaración if. Sin embargo, la definición de la función no transmite la información de que p2 y p3 son opcionales. Si necesita mucha sobrecarga, jQuery ha decidido utilizar un objeto como parámetro, por ejemplo, jQuery.ajax (opciones). Estoy de acuerdo con ellos en que este es el enfoque más poderoso y claramente documentable para la sobrecarga, pero rara vez necesito más de uno o dos parámetros opcionales rápidos.

EDITAR: prueba IF modificada por sugerencia de Ian

cohetes
fuente
16
Los parámetros que no se especifican están undefineden JS, no null. Como práctica recomendada, nunca debe configurar nada undefined, por lo que no debería ser un problema siempre y cuando cambie su prueba p2 === undefined.
Tamzin Blake
3
Si pasa falsecomo último argumento, no se concatenará "false"hasta el final, porque if(p3)no se ramificará.
dreamlax
55
Solo una nota rápida, typeof p2 === "undefined"probablemente sea lo contrario de lo que espera en la instancia de su ejemplo, creo que typeof p2 !== "undefined"es lo que pretendía. Además, ¿puedo sugerir que se supone que concatena la cadena, el número y el booleano que realmente hacesp2 === "number"; p3 === "boolean"
WillFM
8
Me gusta hacer esto: p3 = p3 || 'valor por defecto';
Dorian
3
¿Cuál es el significado de ===y !==? ¿Por qué no solo usar ==y !=?
Ricardo Cruz
76

No hay sobrecarga de funciones reales en JavaScript, ya que permite pasar cualquier número de parámetros de cualquier tipo. Debe verificar dentro de la función cuántos argumentos se han pasado y de qué tipo son.

Gumbo
fuente
1
John Resig (de jQuery) una vez intentó esto, pero el intento fue puramente académico y no proporcionó ningún beneficio real.
scunliffe
14
La sobrecarga de la función de John Resig aquí ejohn.org/blog/javascript-method-overloading
Terrance
@Terrance: también me gusta el método de Resig. Funciona a las mil maravillas. Solo necesito encontrar una manera de crear una prueba para validar casos de uso.
chrisvillanueva
"Esta función no cambiará el mundo, pero es breve, concisa y utiliza una característica oscura de JavaScript, por lo que gana en mi libro". :-)
Frerich Raabe
68

La respuesta correcta es NO HAY SOBRECARGA EN JAVASCRIPT.

Comprobar / Cambiar dentro de la función no es SOBRECARGA.

El concepto de sobrecarga: en algunos lenguajes de programación, la sobrecarga de funciones o la sobrecarga de métodos es la capacidad de crear múltiples métodos del mismo nombre con diferentes implementaciones. Las llamadas a una función sobrecargada ejecutarán una implementación específica de esa función apropiada para el contexto de la llamada, permitiendo que una llamada de función realice diferentes tareas dependiendo del contexto.

Por ejemplo, doTask () y doTask (objeto O) son métodos sobrecargados. Para llamar a este último, se debe pasar un objeto como parámetro, mientras que el primero no requiere un parámetro y se llama con un campo de parámetro vacío. Un error común sería asignar un valor predeterminado al objeto en el segundo método, lo que resultaría en un error de llamada ambiguo, ya que el compilador no sabría cuál de los dos métodos usar.

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

Todas las implementaciones sugeridas son geniales, pero a decir verdad, no existe una implementación nativa para JavaScript.

Marco
fuente
44
finalmente una respuesta normal! NO HAY SOBRECARGA EN JAVASCRIPT.
Razvan Tudorica
44
OP solicitó una forma de falsificar la sobrecarga.
Ateur Games
Como dije antes, estamos aquí para educar a las personas, no solo les damos respuestas sin verificar que lo que están preguntando es correcto.
Marco
27

Hay dos formas de abordar esto mejor:

  1. Pase un diccionario (matriz asociativa) si desea dejar mucha flexibilidad

  2. Tome un objeto como argumento y use una herencia basada en prototipos para agregar flexibilidad.

t3rse
fuente
1
este fue mi pensamiento inicial, sin embargo, si la función que está creando debe ser utilizada en una biblioteca o por otros, enumerar los valores puede ser útil
roberthuttinger
19

Aquí hay un enfoque que permite la sobrecarga de métodos reales utilizando los tipos de parámetros, que se muestran a continuación:

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

Editar (2018) : desde que esto se escribió en 2011, la velocidad de las llamadas a métodos directos ha aumentado considerablemente, mientras que la velocidad de los métodos sobrecargados no.

No es un enfoque que recomendaría, pero vale la pena pensar en cómo puede resolver este tipo de problemas.


Aquí hay un punto de referencia de los diferentes enfoques: https://jsperf.com/function-overloading . Muestra que la sobrecarga de funciones (teniendo en cuenta los tipos) puede ser aproximadamente 13 veces más lenta en el V8 de Google Chrome a partir de 16.0 (beta) .

Además de pasar un objeto (es decir {x: 0, y: 0}), también se puede adoptar el enfoque C cuando sea apropiado, nombrando los métodos en consecuencia. Por ejemplo, Vector.AddVector (vector), Vector.AddIntegers (x, y, z, ...) y Vector.AddArray (integerArray). Puede mirar las bibliotecas C, como OpenGL para nombrar la inspiración.

Editar : agregué un punto de referencia para pasar un objeto y probarlo usando ambos 'param' in argy arg.hasOwnProperty('param'), y la sobrecarga de funciones es mucho más rápida que pasar un objeto y verificar las propiedades (al menos en este punto de referencia).

Desde una perspectiva de diseño, la sobrecarga de funciones solo es válida o lógica si los parámetros sobrecargados corresponden a la misma acción. Por lo tanto, es lógico pensar que debería haber un método subyacente que solo se ocupe de detalles específicos, de lo contrario, eso podría indicar elecciones de diseño inapropiadas. Por lo tanto, también se podría resolver el uso de la sobrecarga de funciones mediante la conversión de datos a un objeto respectivo. Por supuesto, uno debe considerar el alcance del problema, ya que no es necesario hacer diseños elaborados si su intención es solo imprimir un nombre, pero para el diseño de marcos y bibliotecas tal pensamiento está justificado.

Mi ejemplo proviene de una implementación de Rectángulo, de ahí la mención de Dimensión y Punto. Tal vez rectángulo podría añadir un GetRectangle()método para el Dimensiony Pointprototipo, y luego la sobrecarga de funciones problema se solucionó. ¿Y qué hay de los primitivos? Bueno, tenemos una longitud de argumento, que ahora es una prueba válida ya que los objetos tienen un 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
fuente
16

La mejor manera realmente depende de la función y los argumentos. Cada una de sus opciones es una buena idea en diferentes situaciones. Generalmente intento estos en el siguiente orden hasta que uno de ellos funciona:

  1. Usando argumentos opcionales como y = y || 'defecto'. Esto es conveniente si puede hacerlo, pero puede que no siempre funcione prácticamente, por ejemplo, cuando 0 / nulo / indefinido sería un argumento válido.

  2. Usando el número de argumentos. Similar a la última opción, pero puede funcionar cuando el # 1 no funciona.

  3. Comprobación de tipos de argumentos. Esto puede funcionar en algunos casos donde el número de argumentos es el mismo. Si no puede determinar los tipos de manera confiable, es posible que deba usar nombres diferentes.

  4. Usando diferentes nombres en primer lugar. Es posible que deba hacer esto si las otras opciones no funcionan, no son prácticas o si son coherentes con otras funciones relacionadas.

Matthew Crumley
fuente
14

Si necesitaba una función con dos usos, foo (x) y foo (x, y, z), ¿cuál es la mejor / preferida forma?

El problema es que JavaScript NO admite de forma nativa la sobrecarga de métodos. Entonces, si ve / analiza dos o más funciones con los mismos nombres, solo considerará la última función definida y sobrescribirá las anteriores.

Una de las formas en que creo que es adecuada para la mayoría de los casos es la siguiente:

Digamos que tienes un método

function foo(x)
{
} 

En lugar de sobrecargar el método que no es posible en JavaScript , puede definir un nuevo método

fooNew(x,y,z)
{
}

y luego modifique la primera función de la siguiente manera:

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

Si tiene muchos de estos métodos sobrecargados, considere usar switchsolo if-elsedeclaraciones.

( más detalles )

PD: el enlace de arriba va a mi blog personal que tiene detalles adicionales.

Aniket Thakur
fuente
9

No estoy seguro de las mejores prácticas, pero así es como lo hago:

/*
 * 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"
tela de ajedrez
fuente
2
@Luis: He agregado algunos comentarios que espero sean útiles.
chessweb
6

Acabo de probar esto, tal vez se adapte a tus necesidades. Dependiendo del número de argumentos, puede acceder a una función diferente. Lo inicializa la primera vez que lo llama. Y el mapa de funciones está oculto en el cierre.

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;    
};

Pruébalo.

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

Dado que JavaScript no tiene funciones, el objeto de opciones de sobrecarga se puede usar en su lugar. Si hay uno o dos argumentos requeridos, es mejor mantenerlos separados del objeto de opciones. Aquí hay un ejemplo sobre cómo usar el objeto de opciones y los valores poblados al valor predeterminado en caso de que el valor no se haya pasado en el objeto de opciones.

    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 + "}";

}

Aquí hay un ejemplo sobre cómo usar el objeto de opciones

Vlad Bezden
fuente
4

No hay forma de funcionar con sobrecarga en javascript. Entonces, recomiendo lo siguiente por typeof()método en lugar de función múltiple para falsificar la sobrecarga.

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!!");
     }
}

¡Buena suerte!

Smith Lee
fuente
24
¡Por el amor de Dios! ¡Use una declaración de cambio!
jedmao
8
Además, si insiste en no usar un interruptor, solo debe llamar a typeof una vez. var type = typeof param; if (type === 'string') ...
Walter Stabosz
+1 para comentar para el "===". La otra ventaja de la instrucción switch sobre if (... == ...) es que es de tipo seguro.
Nathan Cooper el
4

INTRODUCCIÓN

Hasta ahora, leer tantas respuestas le daría dolor de cabeza a cualquiera. Cualquiera que intente conocer el concepto necesitaría conocer los siguientes requisitos previos .

Function overloading Definition` Function Length property`Function argument property

Function overloadingen su forma más simple significa que una función realiza diferentes tareas en función del número de argumentos que se le pasan. En particular, TASK1, TASK2 y TASK3 se destacan a continuación y se realizan en función del número de pases argumentsa la misma función 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

NOTA : JS no proporciona la capacidad incorporada de sobrecarga de funciones.

Alternativa

John E Resig (creador de JS) ha señalado una alternativa que utiliza los requisitos previos anteriores para lograr la capacidad de implementar la sobrecarga de funciones.

El siguiente código utiliza un enfoque sencillo pero ingenuo mediante el uso de if-elseo switchdeclaración.

  • evalúa la argument-lengthpropiedad.
  • diferentes valores dan como resultado la invocación de diferentes funciones.

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 ...
    } 
  }
}

Otra técnica es mucho más limpia y dinámica. Lo más destacado de esta técnica es la addMethodfunción genérica.

  • definimos una función addMethodque se usa para agregar diferentes funciones a un objeto con el mismo nombre pero con diferentes funcionalidades .

  • debajo de la addMethodfunción acepta el nombre del objeto de tres parámetros object, el nombre de namela función y la función que queremos que se invoque fn.

  • La addMethoddefinición interna var oldalmacena la referencia a lo anterior functionalmacenado con la ayuda del cierre, una burbuja protectora.

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 el depurador para comprender el flujo del código.
  • debajo de addMethodagrega tres funciones que, cuando se invoca, se utiliza ninja.whatever(x)con el número de argumentos xque pueden ser cualquier cosa, es decir, en blanco o una o más de una, invoca diferentes funciones como se define al hacer uso de la addMethodfunción.

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
fuente
4

Otra forma de abordar esto es mediante el uso de la variable especial: argumentos , esta es una implementación:

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

para que pueda modificar este código para:

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

mira esto. Esta muy padre. http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript para permitirle hacer llamadas como esta:

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
fuente
Hola Jaider, mira mi respuesta, contiene código para la sobrecarga del método javascript real . Estoy hablando Func(new Point())y Func(new Rectangle())ejecutaré diferentes funciones. Pero debo señalar que este es un truco sucio, ya que la sobrecarga de métodos es realmente una tarea de tiempo de compilación, no de tiempo de ejecución.
Keldon Alleyne
3

Sobrecarga de funciones en Javascript:

La sobrecarga de funciones es la capacidad de un lenguaje de programación para crear múltiples funciones del mismo nombre con diferentes implementaciones. cuando se llama a una función sobrecargada, ejecutará una implementación específica de esa función apropiada para el contexto de la llamada. Este contexto suele ser la cantidad de argumentos recibidos, y permite que una llamada de función se comporte de manera diferente según el contexto.

Javascript no tiene una función de sobrecarga incorporada. Sin embargo, este comportamiento se puede emular de muchas maneras. Aquí hay una conveniente simple:

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

En escenarios en los que no sabes cuántos argumentos obtendrás, puedes usar el operador rest, que son tres puntos .... Convertirá el resto de los argumentos en una matriz. Sin embargo, tenga cuidado con la compatibilidad del navegador. Aquí hay un ejemplo:

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

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

Willem van der Veen
fuente
2

Como esta publicación ya contiene muchas soluciones diferentes, pensé que publicaría otra.

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

}

esto se puede usar como se muestra a continuación:

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 solución no es perfecta, pero solo quiero demostrar cómo se puede hacer.

remyH
fuente
2

Puede usar el 'addMethod' de John Resig. Con este método puede "sobrecargar" los métodos basados ​​en el recuento 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 );
    };
}

También he creado una alternativa a este método que utiliza el almacenamiento en caché para contener las variaciones de la función. Aquí se describen las diferencias.

// 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
fuente
2

Patrón de reenvío => la mejor práctica en sobrecarga de JS

Reenviar a otra función cuyo nombre se construye a partir de los puntos tercero y cuarto:

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

Solicitud en su 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(){

  }

Otra muestra compleja:

      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
fuente
2

Sobrecarga de funciones mediante polimorfismo dinámico en 100 líneas de JS

Esto es de un cuerpo más grande de código que incluye los isFn, isArr, etc. funciones comprobación de tipos. La versión de VanillaJS a continuación ha sido modificada para eliminar todas las dependencias externas, sin embargo, tendrá que definir sus propias funciones de verificación de tipo para usar en las .add()llamadas.

Nota: Esta es una función de ejecución automática (por lo que podemos tener un alcance cerrado / cerrado), de ahí la asignación a window.overloadmás que a 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:

La persona que llama define sus funciones sobrecargadas asignando una variable al retorno de overload(). Gracias al encadenamiento, las sobrecargas adicionales se pueden definir en serie:

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

El argumento opcional único para overload()definir la función "predeterminada" para llamar si no se puede identificar la firma. Los argumentos para .add()son:

  1. fn: functiondefinición de la sobrecarga;
  2. a_vArgumentTests: Arrayde functions definiendo las pruebas a ejecutar en el arguments. Cada uno functionacepta un único argumento y devuelve truetu en función de si el argumento es válido;
  3. sAlias(Opcional): stringdefinir el alias para acceder directamente a la función de sobrecarga ( fn), por ejemplo myOverloadedFn.noArgs(), llamará a esa función directamente, evitando las pruebas de polimorfismo dinámico de los argumentos.

Esta implementación en realidad permite más que solo sobrecargas de funciones tradicionales como el segundo a_vArgumentTestsargumento para .add()definir en la práctica tipos personalizados. Por lo tanto, ¡podría abrir argumentos no solo en función del tipo, sino también de rangos, valores o colecciones de valores!

Si mira a través de las 145 líneas de código overload(), verá que cada firma se clasifica por el número de argumentspasadas. Esto se hace para limitar el número de pruebas que estamos ejecutando. También llevo un registro del recuento de llamadas. Con algún código adicional, las matrices de funciones sobrecargadas podrían reorganizarse para que las funciones más comúnmente llamadas se prueben primero, agregando nuevamente alguna medida de mejora del rendimiento.

Ahora, hay algunas advertencias ... Como Javascript está mal escrito, tendrá que tener cuidado con su, vArgumentTestsya que integerpodría validarse como float, etc.

Versión JSCompress.com (1114 bytes, 744 bytes g-zipped):

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
fuente
2

Ahora puede hacer una sobrecarga de funciones en ECMAScript 2018 sin polyfills, verificar la longitud / tipo de var, etc., solo use la sintaxis de propagación .

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"

¿Qué es la sintaxis extendida?

La propuesta de propiedades de descanso / propagación para ECMAScript (etapa 4) agrega propiedades de propagación a literales de objeto. Copia propiedades enumerables propias de un objeto proporcionado en un nuevo objeto. Más sobre mdn

Nota: la sintaxis extendida en literales de objetos no funciona en Edge e IE y es una característica experimental. ver compatibilidad del navegador

AlienKevin
fuente
2

Algo como esto se puede hacer para la sobrecarga de funciones.

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"
});

Fuente

Supun Kavinda
fuente
1

Esta es una pregunta antigua, pero creo que necesita otra entrada (aunque dudo que alguien la lea). El uso de Expresiones de funciones invocadas inmediatamente (IIFE) se puede usar junto con cierres y funciones en línea para permitir la sobrecarga de funciones. Considere el siguiente ejemplo (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

En resumen, el uso del IIFE crea un ámbito local, lo que nos permite definir la variable privada oldpara almacenar una referencia a la definición inicial de la función foo. Esta función devuelve una función en línea newFooque registra el contenido de ambos dos argumentos si se pasa exactamente dos argumentos ay bo llama a la oldfunción si arguments.length !== 2. Este patrón se puede repetir cualquier cantidad de veces para dotar a una variable con varias definiciones funcionales diferentes.

wvandaal
fuente
1

Me gustaría compartir un ejemplo útil de un enfoque sobrecargado.

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: Claro (); // borra todo el documento

Claro (myDiv); // Borra el panel al que hace referencia myDiv

Alexander Chernosvitov
fuente
1

JavaScript es un lenguaje sin tipo, y solo creo que tiene sentido sobrecargar un método / función con respecto al número de parámetros. Por lo tanto, recomendaría verificar si el parámetro ha sido definido:

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};
Tony
fuente
1
Solo quiero notar que sin tipo no significa "sin tipos".
hamdiakoguz
1

A partir de julio de 2017, la siguiente ha sido la técnica común. Tenga en cuenta que también podemos realizar verificaciones de tipo dentro de la función.

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
fuente
1

Para su caso de uso, así es como lo abordaría ES6(ya que es a finales 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, puede adaptar esto para trabajar con cualquier cantidad de parámetros y simplemente cambiar sus declaraciones condicionales en consecuencia.

Barry Michael Doyle
fuente
1

no hay sobrecarga real en JS, de todos modos todavía podemos simular la sobrecarga de métodos de varias maneras:

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 los 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: uso indefinido

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

método # 4: verificación de tipo

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

Si bien los parámetros predeterminados no se están sobrecargando, podrían resolver algunos de los problemas que enfrentan los desarrolladores en esta área. La entrada está estrictamente decidida por el pedido, no puede reordenar como desee como en la sobrecarga clásica:

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 en (para el año 2020):

2020
16160
12
65
2021
2023

Más información: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

Karl Morrison
fuente
0

La primera opción realmente merece atención porque es lo que he encontrado en una configuración de código bastante compleja. Entonces mi respuesta es

  1. Usando diferentes nombres en primer lugar

Con una pequeña pero esencial pista, los nombres deberían ser diferentes para la computadora, pero no para ti. Nombre funciones sobrecargadas como: func, func1, func2.

alehro
fuente
Iba a intentar sobrecargar pero decidí usar nombres diferentes, por ejemplo, getDeviceInfoByID y getDeviceInfoByType ...
CramerTV