Surcharge de fonction en Javascript - Meilleures pratiques

784

Quelle (s) meilleure (s) manière (s) de simuler la surcharge de fonctions en Javascript?

Je sais qu'il n'est pas possible de surcharger les fonctions en Javascript comme dans d'autres langues. Si j'avais besoin d'une fonction à deux utilisations foo(x)et foo(x,y,z)qui est la meilleure / préférée:

  1. Utiliser des noms différents en premier lieu
  2. Utilisation d'arguments facultatifs comme y = y || 'default'
  3. Utilisation d'un nombre d'arguments
  4. Vérification des types d'arguments
  5. Ou comment?
hamdiakoguz
la source
14
Il serait peut-être utile de se demander pourquoi vous pensez avoir besoin d'une surcharge de fonctions pour commencer. Je pense que cela nous rapprochera d'une véritable solution.
Breton
1
Ceci est fermé, mais je fais ce qui suit: this.selectBy = {instance: selectByInstance, // Texte de fonction: selectByText, // Valeur de fonction: selectByValue // Function};
Prisonnier ZERO
Ma réponse montre comment faire une surcharge de la fonction d'exécution, il a une pénalité de vitesse et je ne conseillerais pas de le faire pour contourner les spécifications de Javascript. La surcharge de fonctions est vraiment une tâche de compilation, je ne donne la réponse qu'à des fins académiques et je laisse à votre discrétion le soin de l'utiliser ou non dans le code.
Keldon Alleyne
2
Juste au cas où cela serait utile, j'ai construit un framework js léger qui permet une surcharge de méthode basée sur le type. Évidemment, les mêmes avertissements s'appliquent en ce qui concerne les performances, mais cela a bien fonctionné pour mes besoins jusqu'à présent et a encore beaucoup de place à l'amélioration: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Réponses:

603

La meilleure façon de surcharger les fonctions avec des paramètres n'est pas de vérifier la longueur des arguments ou les types; vérifier les types ne fera que ralentir votre code et vous vous amuserez avec les tableaux, les valeurs nulles, les objets, etc.

Ce que la plupart des développeurs font, c'est plaquer un objet comme le dernier argument de leurs méthodes. Cet objet peut contenir n'importe quoi.

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

Ensuite, vous pouvez le gérer comme vous le souhaitez dans votre méthode. [Switch, if-else, etc.]

epascarello
la source
43
Pourriez-vous fournir un exemple d'implémentation de foo () qui illustre comment ces paramètres "opts" sont utilisés / référencés?
Moe Howard
24
Moe // Ça pourrait être comme ça; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard
80
Ce n'est pas une surcharge de fonction. La surcharge de fonctions comporte deux fonctions distinctes portant le même nom mais des paramètres différents. Ce que vous décrivez n'est qu'une fonction avec un argument d'objet à la fin.
d512
66
@ user1334007 il est impossible d'avoir une surcharge de fonction comme vous le feriez en Java / .NET. Oui, ce n'est pas une surcharge "exacte", mais cela fait l'affaire.
epascarello
18
Je suis surpris que personne ne l'ait déjà demandé: pourquoi la vérification arguments.lengthn'est-elle pas recommandée? De plus, j'ai déjà été ici et j'ai lu ce que la plupart des développeurs font ... , mais je suis sûr que c'est le seul endroit où j'ai vu cela se faire. Cette méthode gâche également la douceur syntaxique d'avoir des «surcharges»!
c24w
169

Je fais souvent ça:

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

Équivalent 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

Cet exemple particulier est en fait plus élégant en javascript que C #. Les paramètres qui ne sont pas spécifiés sont «non définis» en javascript, qui est évalué à faux dans une instruction if. Cependant, la définition de la fonction ne transmet pas les informations selon lesquelles p2 et p3 sont facultatifs. Si vous avez besoin de beaucoup de surcharge, jQuery a décidé d'utiliser un objet comme paramètre, par exemple, jQuery.ajax (options). Je suis d'accord avec eux qu'il s'agit de l'approche de surcharge la plus puissante et la plus clairement documentable, mais j'ai rarement besoin de plus d'un ou deux paramètres optionnels rapides.

EDIT: modification du test IF selon la suggestion de Ian

fusées rapides
la source
16
Les paramètres qui ne sont pas spécifiés sont undefineden JS, non null. En tant que meilleure pratique, vous ne devez jamais définir quoi que ce soit undefined, donc cela ne devrait pas être un problème tant que vous changez votre test en p2 === undefined.
Tamzin Blake
3
Si vous passez falsecomme dernier argument, il ne sera pas concaténé "false"à la fin, car la if(p3)branche ne sera pas.
dreamlax
5
Juste une note rapide, votre typeof p2 === "undefined"est probablement l'inverse de ce que vous attendez dans l'exemple de votre exemple, je pense que typeof p2 !== "undefined"c'est ce que vous vouliez. De plus, puis-je suggérer d'être censé concaténer la chaîne, le nombre et le booléen que vous faites réellementp2 === "number"; p3 === "boolean"
WillFM
8
J'aime faire ça: p3 = p3 || 'valeur par défaut';
Dorian
3
Quelle est la signification de ===et !==? Pourquoi ne pas simplement utiliser ==et !=?
Ricardo Cruz
76

Il n'y a pas de surcharge de fonction réelle en JavaScript car il permet de passer un nombre illimité de paramètres de tout type. Vous devez vérifier dans la fonction combien d' arguments ont été passés et de quel type ils sont.

Gombo
la source
1
John Resig (de jQuery) a essayé une fois, mais la tentative était purement académique et n'a apporté aucun avantage réel.
scunliffe
14
La surcharge de fonctions de John Resig ici ejohn.org/blog/javascript-method-overloading
Terrance
@Terrance: J'aime aussi la méthode de Resig. Il fonctionne comme un charme. J'ai juste besoin de trouver un moyen de créer un test pour valider les cas d'utilisation.
chrisvillanueva
"Cette fonction ne changera pas le monde, mais elle est courte, concise et utilise une fonction JavaScript obscure - elle gagne donc dans mon livre." :-)
Frerich Raabe
68

La bonne réponse est QU'IL N'Y A PAS DE SURCHARGE DANS JAVASCRIPT.

Vérifier / commuter à l'intérieur de la fonction n'est pas SURCHARGER.

Le concept de surcharge: Dans certains langages de programmation, la surcharge de fonctions ou la surcharge de méthodes est la possibilité de créer plusieurs méthodes du même nom avec différentes implémentations. Les appels à une fonction surchargée exécuteront une implémentation spécifique de cette fonction appropriée au contexte de l'appel, permettant à un appel de fonction d'effectuer différentes tâches selon le contexte.

Par exemple, doTask () et doTask (objet O) sont des méthodes surchargées. Pour appeler ce dernier, un objet doit être passé en paramètre, tandis que le premier ne nécessite pas de paramètre et est appelé avec un champ de paramètre vide. Une erreur courante serait d'attribuer une valeur par défaut à l'objet dans la deuxième méthode, ce qui entraînerait une erreur d'appel ambiguë, car le compilateur ne saurait pas laquelle des deux méthodes utiliser.

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

Toutes les implémentations suggérées sont excellentes, mais à vrai dire, il n'y a pas d'implémentation native pour JavaScript.

Marco
la source
4
enfin une réponse normale! IL N'Y A PAS DE SURCHARGE À JAVASCRIPT.
Razvan Tudorica
4
OP a demandé un moyen de simuler la surcharge.
Ateur Games
Comme je l'ai déjà dit, nous sommes ici pour éduquer les gens, nous ne leur donnons pas seulement des réponses sans vérifier que ce qu'ils demandent est juste.
Marco
27

Il y a deux façons de mieux aborder cela:

  1. Passez un dictionnaire (tableau associatif) si vous voulez laisser beaucoup de flexibilité

  2. Prenez un objet comme argument et utilisez l'héritage basé sur un prototype pour ajouter de la flexibilité.

t3rse
la source
1
c'était ma pensée initiale, cependant, si la fonction que vous créez doit être utilisée dans une bibliothèque ou par d'autres, énumérer clairement les valeurs peut être utile
roberthuttinger
19

Voici une approche qui permet une surcharge de méthode réelle à l'aide de types de paramètres, illustrés ci-dessous:

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

Edit (2018) : depuis que cela a été écrit en 2011, la vitesse des appels de méthode directe a considérablement augmenté alors que la vitesse des méthodes surchargées ne l'a pas fait.

Ce n'est pas une approche que je recommanderais, mais c'est un exercice de réflexion utile pour réfléchir à la façon de résoudre ces types de problèmes.


Voici une référence des différentes approches - https://jsperf.com/function-overloading . Cela montre que la surcharge des fonctions (en tenant compte des types) peut être environ 13 fois plus lente dans la version 8 de Google Chrome à partir de 16.0 (bêta) .

En plus de passer un objet (c'est-à-dire {x: 0, y: 0}), on peut également adopter l'approche C le cas échéant, en nommant les méthodes en conséquence. Par exemple, Vector.AddVector (vecteur), Vector.AddIntegers (x, y, z, ...) et Vector.AddArray (integerArray). Vous pouvez regarder les bibliothèques C, telles que OpenGL pour l'inspiration de nommage.

Edit : J'ai ajouté un benchmark pour passer un objet et tester l'objet en utilisant les deux 'param' in arget arg.hasOwnProperty('param'), et la surcharge de fonction est beaucoup plus rapide que de passer un objet et de vérifier les propriétés (dans ce benchmark au moins).

Du point de vue de la conception, la surcharge de fonction n'est valide ou logique que si les paramètres surchargés correspondent à la même action. Il va donc de soi qu'il devrait y avoir une méthode sous-jacente qui ne concerne que des détails spécifiques, sinon cela pourrait indiquer des choix de conception inappropriés. Ainsi, on pourrait également résoudre l'utilisation de la surcharge de fonctions en convertissant les données en un objet respectif. Bien sûr, il faut considérer l'ampleur du problème car il n'est pas nécessaire de faire des conceptions élaborées si votre intention est simplement d'imprimer un nom, mais pour la conception de cadres et de bibliothèques, une telle pensée est justifiée.

Mon exemple vient d'une implémentation Rectangle - d'où la mention de Dimension et Point. Rectangle pourrait peut - être ajouter une GetRectangle()méthode au Dimensionet Pointprototype, puis la question de surcharge fonction est triée. Et les primitifs? Eh bien, nous avons la longueur d'argument, qui est maintenant un test valide car les objets ont une GetRectangle()méthode.

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
la source
16

La meilleure façon dépend vraiment de la fonction et des arguments. Chacune de vos options est une bonne idée dans différentes situations. J'essaye généralement ceux-ci dans l'ordre suivant jusqu'à ce que l'un d'eux fonctionne:

  1. Utilisation d'arguments optionnels comme y = y || 'défaut'. C'est pratique si vous pouvez le faire, mais cela peut ne pas toujours fonctionner pratiquement, par exemple quand 0 / null / undefined serait un argument valide.

  2. Utilisation d'un certain nombre d'arguments. Similaire à la dernière option, mais peut fonctionner lorsque # 1 ne fonctionne pas.

  3. Vérification des types d'arguments. Cela peut fonctionner dans certains cas où le nombre d'arguments est le même. Si vous ne pouvez pas déterminer de manière fiable les types, vous devrez peut-être utiliser des noms différents.

  4. Utiliser des noms différents en premier lieu. Vous devrez peut-être le faire si les autres options ne fonctionnent pas, ne sont pas pratiques ou pour assurer la cohérence avec d'autres fonctions connexes.

Matthew Crumley
la source
14

Si j'avais besoin d'une fonction avec deux utilisations foo (x) et foo (x, y, z), quel est le meilleur moyen / préféré?

Le problème est que JavaScript ne prend pas en charge de manière native la surcharge de méthode. Donc, s'il voit / analyse deux fonctions ou plus avec les mêmes noms, il considérera simplement la dernière fonction définie et écrasera les précédentes.

Je pense que l'un des moyens qui convient à la plupart des cas est le suivant -

Disons que vous avez de la méthode

function foo(x)
{
} 

Au lieu de surcharger la méthode qui n'est pas possible en javascript, vous pouvez définir une nouvelle méthode

fooNew(x,y,z)
{
}

puis modifiez la 1ère fonction comme suit -

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

Si vous avez plusieurs de ces méthodes surchargées, envisagez d'utiliser plus switchque des if-elseinstructions.

( plus de détails )

PS: Le lien ci-dessus va à mon blog personnel qui contient des détails supplémentaires.

Aniket Thakur
la source
9

Je ne suis pas sûr des meilleures pratiques, mais voici comment je le fais:

/*
 * 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"
échiquier
la source
2
@Luis: J'ai ajouté des commentaires, je l'espère utiles.
chessweb
6

Je viens d'essayer cela, peut-être que cela convient à vos besoins. Selon le nombre d'arguments, vous pouvez accéder à une fonction différente. Vous l'initialisez la première fois que vous l'appelez. Et la carte des fonctions est cachée dans la fermeture.

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

Essaye-le.

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

Puisque JavaScript n'a pas d'options de surcharge de fonction, l'objet peut être utilisé à la place. S'il y a un ou deux arguments requis, il est préférable de les garder séparés de l'objet options. Voici un exemple sur la façon d'utiliser l'objet options et les valeurs remplies à la valeur par défaut au cas où la valeur n'a pas été transmise dans l'objet options.

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

}

voici un exemple sur la façon d'utiliser l'objet options

Vlad Bezden
la source
4

Il n'y a aucun moyen de faire fonctionner la surcharge en javascript. Donc, je recommande comme la typeof()méthode suivante au lieu de plusieurs fonctions pour simuler la surcharge.

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

Bonne chance!

Smith Lee
la source
24
Pour l'amour de Dieu! Utilisez une instruction switch!
jedmao
8
De plus, si vous insistez pour ne pas utiliser de commutateur, vous ne devez appeler typeof qu'une seule fois. var type = typeof param; if (type === 'string') ...
Walter Stabosz
+1 pour commenter le "===". L'autre avantage de l'instruction switch sur si (... == ...) est son type-safe.
Nathan Cooper
4

INTRODUCTION

Jusqu'à présent, la lecture de tant de réponses donnerait mal à tout le monde. Quiconque essaie de connaître le concept devrait connaître les conditions préalables suivantes :

Function overloading Definition, Function Length property,Function argument property

Function overloadingdans sa forme la plus simple, une fonction exécute différentes tâches en fonction du nombre d'arguments qui lui sont transmis. Notamment, TASK1, TASK2 et TASK3 sont mis en évidence ci-dessous et sont exécutés sur la base du nombre de passages argumentsà la même fonction 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

REMARQUE - JS ne fournit pas de capacité intégrée de surcharge de fonctions.

Alternative

John E Resig (créateur de JS) a souligné une alternative qui utilise les conditions préalables ci-dessus pour obtenir la capacité d'implémenter la surcharge de fonctions.

Le code ci-dessous utilise une approche simple mais naïve en utilisant if-elseou une switchdéclaration.

  • évalue la argument-lengthpropriété.
  • des valeurs différentes entraînent l'invocation de différentes fonctions.

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

Une autre technique est beaucoup plus propre et dynamique. Le point culminant de cette technique est la addMethodfonction générique.

  • nous définissons une fonction addMethodqui est utilisée pour ajouter différentes fonctions à un objet avec le même nom mais des fonctionnalités différentes .

  • au-dessous de la addMethodfonction accepte trois nom d'objet params object, nom de fonction nameet la fonction que nous voulons invoquer fn.

  • La addMethoddéfinition intérieure var oldstocke la référence au précédent functionstocké à l'aide de la fermeture - une bulle de protection.

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

  • utilisez le débogueur pour comprendre le flux de code.
  • au-dessous de l' addMethodajout de trois fonctions qui, lorsqu'elles sont invoquées en utilisant ninja.whatever(x)avec le nombre d'arguments xqui peuvent être n'importe quoi, c'est-à-dire soit vide, soit une ou plusieurs invoque différentes fonctions telles que définies tout en utilisant la addMethodfonction.

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
la source
4

Une autre façon d'aborder cela est d'utiliser la variable spéciale: arguments , c'est une implémentation:

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

vous pouvez donc modifier ce code pour:

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

regarde ça. C'est très cool. http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript pour vous permettre de faire des appels comme celui-ci:

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
la source
Salut Jaider, consultez ma réponse, elle contient du code pour la surcharge réelle de la méthode javascript. Je parle Func(new Point())et Func(new Rectangle())exécuterai différentes fonctions. Mais je dois souligner qu'il s'agit d'un hack sale, car la surcharge de méthode est vraiment une tâche de compilation et non d'exécution.
Keldon Alleyne
3

Surcharge de fonction en Javascript:

La surcharge de fonctions est la capacité d'un langage de programmation à créer plusieurs fonctions du même nom avec différentes implémentations. lorsqu'une fonction surchargée est appelée, elle exécute la fonction une implémentation spécifique de cette fonction appropriée au contexte de l'appel. Ce contexte est généralement la quantité d'arguments reçue, et il permet à un appel de fonction de se comporter différemment selon le contexte.

Javascript n'a pas de surcharge de fonction intégrée. Cependant, ce comportement peut être imité de plusieurs façons. En voici une simple et pratique:

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

Dans les scénarios où vous ne savez pas combien d'arguments vous obtiendrez, vous pouvez utiliser l' opérateur rest qui est de trois points .... Il convertira le reste des arguments en un tableau. Attention cependant à la compatibilité du navigateur. Voici un exemple:

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

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

Willem van der Veen
la source
2

Comme cet article contient déjà de nombreuses solutions différentes, j'ai pensé en poster une autre.

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

}

cela peut être utilisé comme indiqué ci-dessous:

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

Cette solution n'est pas parfaite mais je veux seulement montrer comment cela pourrait être fait.

remyH
la source
2

Vous pouvez utiliser le «addMethod» de John Resig. Avec cette méthode, vous pouvez "surcharger" les méthodes en fonction du nombre d'arguments.

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

J'ai également créé une alternative à cette méthode qui utilise la mise en cache pour contenir les variations de la fonction. Les différences sont décrites ici

// 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
la source
2

Modèle de transfert => la meilleure pratique de surcharge JS

Transférer à une autre fonction dont le nom est construit à partir des 3e et 4e points:

  1. Utilisation d'un nombre d'arguments
  2. Vérification des types d'arguments
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

Application sur votre cas:

 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(){

  }

Autre échantillon complexe:

      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
la source
2

Surcharge de fonction via le polymorphisme dynamique dans 100 lignes de JS

Ceci est d'un plus grand corps de code qui comprend les isFn, isArretc. fonctions de contrôle de type. La version VanillaJS ci-dessous a été retravaillée pour supprimer toutes les dépendances externes, mais vous devrez définir vos propres fonctions de vérification de type à utiliser dans les .add()appels.

Remarque: Il s'agit d'une fonction auto-exécutable (nous pouvons donc avoir une portée de fermeture / fermée), d'où l'affectation à window.overloadplutôt qu'à 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;
}();

Usage:

L'appelant définit leurs fonctions surchargées en affectant une variable au retour de overload(). Grâce au chaînage, les surcharges supplémentaires peuvent être définies en 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")
;

Le seul argument facultatif pour overload()définir la fonction "par défaut" à appeler si la signature ne peut pas être identifiée. Les arguments pour .add()sont:

  1. fn: functiondéfinition de la surcharge;
  2. a_vArgumentTests: Arrayde functions définir les tests à exécuter sur le arguments. Chacun functionaccepte un seul argument et retourne truethy selon que l'argument est valide;
  3. sAlias(Facultatif): stringdéfinir l'alias pour accéder directement à la fonction de surcharge ( fn), par exemple myOverloadedFn.noArgs(), appellera cette fonction directement, en évitant les tests de polymorphisme dynamique des arguments.

Cette implémentation permet en fait plus que de simples surcharges de fonctions traditionnelles, car le deuxième a_vArgumentTestsargument .add()définit en pratique les types personnalisés. Ainsi, vous pouvez générer des arguments non seulement en fonction du type, mais en fonction de plages, de valeurs ou de collections de valeurs!

Si vous regardez à travers les 145 lignes de code, overload()vous verrez que chaque signature est classée par le nombre de argumentspassé. Ceci est fait de manière à limiter le nombre de tests que nous exécutons. Je garde également une trace du nombre d'appels. Avec du code supplémentaire, les tableaux de fonctions surchargées pourraient être triés de nouveau afin que les fonctions plus communément appelées soient testées en premier, ajoutant encore une certaine mesure d'amélioration des performances.

Maintenant, il y a quelques mises en garde ... Comme Javascript est vaguement tapé, vous devrez faire attention à votre vArgumentTestscomme un integerpourrait être validé comme un float, etc.

Version JSCompress.com (1114 octets, 744 octets g-zippés):

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
la source
2

Vous pouvez maintenant faire une surcharge de fonctions dans ECMAScript 2018 sans polyfills, vérifier la longueur / le type de var, etc., utilisez simplement la syntaxe de propagation .

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'est-ce que la syntaxe étalée?

La proposition Rest / Spread Properties for ECMAScript (étape 4) ajoute des propriétés de propagation aux littéraux d'objet. Il copie ses propres propriétés énumérables d'un objet fourni vers un nouvel objet. Plus sur mdn

Remarque: la syntaxe répartie dans les littéraux d'objet ne fonctionne pas dans Edge et IE et il s'agit d'une fonctionnalité expérimentale. voir la compatibilité du navigateur

AlienKevin
la source
2

Quelque chose comme ça peut être fait pour la surcharge de fonctions.

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

La source

Supun Kavinda
la source
1

C'est une vieille question mais qui, je pense, a besoin d'une autre entrée (bien que je doute que quelqu'un la lise). L'utilisation d'expressions de fonctions immédiatement appelées (IIFE) peut être utilisée conjointement avec des fermetures et des fonctions en ligne pour permettre une surcharge de fonctions. Prenons l'exemple (artificiel) suivant:

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 bref, l'utilisation de l'IIFF crée une portée locale, nous permettant de définir la variable privée oldpour stocker une référence à la définition initiale de la fonction foo. Cette fonction renvoie ensuite une fonction en ligne newFooqui enregistre le contenu des deux arguments si elle est passée exactement deux arguments aet bou appelle la oldfonction if arguments.length !== 2. Ce modèle peut être répété un certain nombre de fois pour doter une variable de plusieurs définitions fonctionnelles différentes.

wvandaal
la source
1

Je voudrais partager un exemple utile d'approche de type surchargé.

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

Utilisation: Clear (); // Efface tout le document

Clear (myDiv); // Efface le panneau référencé par myDiv

Alexander Chernosvitov
la source
1

JavaScript est un langage non typé, et je pense seulement que cela a du sens de surcharger une méthode / fonction en ce qui concerne le nombre de paramètres. Par conséquent, je recommanderais de vérifier si le paramètre a été défini:

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};
Tony
la source
1
Je veux juste noter que non typé ne signifie pas «pas de types».
hamdiakoguz
1

En juillet 2017, ce qui suit était la technique courante. Notez que nous pouvons également effectuer une vérification de type dans la fonction.

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
la source
1

Pour votre cas d'utilisation, voici comment je m'y attaquerais ES6(puisque c'est déjà la fin 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;
}

Vous pouvez évidemment l'adapter pour qu'il fonctionne avec n'importe quelle quantité de paramètres et modifier simplement vos instructions conditionnelles en conséquence.

Barry Michael Doyle
la source
1

il n'y a pas de surcharge réelle dans JS, de toute façon nous pouvons toujours simuler la surcharge de méthode de plusieurs façons:

méthode # 1: utiliser un objet

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

méthode # 2: utiliser les paramètres de repos (propagation)

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éthode # 3: utilisez undefined

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

méthode # 4: vérification de type

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

Bien que les paramètres par défaut ne surchargent pas, cela pourrait résoudre certains des problèmes auxquels les développeurs sont confrontés dans ce domaine. L'entrée est strictement décidée par la commande, vous ne pouvez pas réorganiser comme vous le souhaitez comme en surcharge classique:

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

Résultats en (pour l'année 2020):

2020
16160
12
65
2021
2023

Plus d'informations: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

Karl Morrison
la source
0

La première option mérite vraiment l'attention, car c'est la chose que j'ai trouvée dans la configuration de code assez complexe. Donc, ma réponse est

  1. Utiliser des noms différents en premier lieu

Avec un petit mais essentiel indice, les noms devraient être différents pour l'ordinateur, mais pas pour vous. Nommez des fonctions surchargées comme: func, func1, func2.

alehro
la source
J'allais essayer de surcharger mais j'ai décidé d'utiliser simplement des noms différents, par exemple getDeviceInfoByID et getDeviceInfoByType ...
CramerTV