web-dev-qa-db-fra.com

Surcharge fonctionnelle en Javascript - Bonnes pratiques

Quel est le meilleur moyen de simuler une surcharge de fonction en Javascript? 

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

  1. Utiliser des noms différents en premier lieu
  2. Utiliser des arguments optionnels comme y = y || 'default'
  3. Utilisation du nombre d'arguments
  4. Vérification des types d'arguments
  5. Ou comment?
690
hamdiakoguz

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

Ce que la plupart des développeurs font, c'est d'utiliser un objet comme 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, si-sinon, etc.]

532
epascarello

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 réalité plus élégant en javascript qu'en C #. Les paramètres non spécifiés sont 'undefined' en javascript, ce qui donne False dans une instruction if. Cependant, la définition de la fonction ne transmet pas l’information que 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 pour dire que c’est l’approche la plus puissante et la plus clairement documentable de la surcharge, 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

150
rocketsarefast

Il n’ya pas de surcharge de fonction réelle dans JavaScript car cela permet de transmettre un nombre quelconque de paramètres de tout type. Vous devez vérifier dans la fonction combien de arguments ont été passés et quel type ils sont.

64
Gumbo

La bonne réponse est: IS PAS DE SURCHARGE DANS JAVASCRIPT.

Vérifier/basculer à l'intérieur de la fonction n'est pas une SURCHARGE.

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

Par exemple, doTask () et doTask (objet O) sont des méthodes surchargées. Pour appeler ce dernier, un objet doit être passé en tant que paramètre, alors 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/overloading

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

46
Marco

Il y a deux façons de mieux aborder cette question: 

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

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

26
t3rse

Voici une approche permettant 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 sa rédaction en 2011, la vitesse des appels de méthodes directes a considérablement augmenté, contrairement à celle des méthodes surchargées.

Ce n’est pas une approche que je recommanderais, mais c’est un exercice de réflexion utile qui permet de réfléchir à la manière de résoudre ce type de problèmes.


Voici un repère des différentes approches - https://jsperf.com/function-overloading . Cela montre que la surcharge de fonctions (en tenant compte des types) peut être environ 13 fois plus lente dans le V8 de Google Chrome à partir de 16.0 (beta).

En plus de transmettre 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 (vector), Vector.AddIntegers (x, y, z, ...) et Vector.AddArray (integerArray). Vous pouvez consulter les bibliothèques C, telles que OpenGL pour nommer l'inspiration. 

Edit: j'ai ajouté un point de repère pour passer un objet et tester l'objet à l'aide de 'param' in arg et de arg.hasOwnProperty('param'), et la surcharge de fonctions est beaucoup plus rapide que de passer un objet et de rechercher des propriétés (au moins dans ce critère).

Du point de vue de la conception, la surcharge de fonctions 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 exister une méthode sous-jacente qui ne concerne que des détails spécifiques, sinon cela peut indiquer des choix de conception inappropriés. Donc, on pourrait également résoudre l'utilisation de la surcharge de fonction en convertissant les données en un objet respectif. Bien entendu, il faut tenir compte de l’ampleur du problème, car il n’est pas nécessaire de créer des motifs élaborés si vous souhaitez uniquement imprimer un nom, mais cette conception est justifiée pour la conception de cadres et de bibliothèques.

Mon exemple vient d'une implémentation Rectangle - d'où la mention de Dimension et Point. Peut-être que Rectangle pourrait ajouter une méthode GetRectangle() au prototype Dimension et Point, puis le problème de surcharge de fonctions est trié. Et qu'en est-il des primitifs? Nous avons bien la longueur des arguments, qui est maintenant un test valide puisque les objets ont une méthode GetRectangle().

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);
17
Keldon Alleyne

Le meilleur moyen dépend vraiment de la fonction et des arguments. Chacune de vos options est une bonne idée dans différentes situations. J'essaie généralement de les essayer dans l'ordre suivant jusqu'à ce que l'un d'entre eux fonctionne:

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

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

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

  4. Utiliser des noms différents en premier lieu. Vous devrez peut-être faire cela si les autres options ne fonctionnent pas, si elles ne sont pas pratiques ou si elles sont compatibles avec d'autres fonctions connexes.

16
Matthew Crumley

Si j'avais besoin d'une fonction avec deux utilisations foo (x) et foo (x, y, z), quelle est la meilleure méthode/la méthode préférée?

Le problème est que JavaScript ne supporte PAS nativement la surcharge de méthodes. Ainsi, s’il voit/analyse deux ou plusieurs fonctions portant le même nom, il considérera simplement la dernière fonction définie et écrasera les précédentes.

Je pense que l’une des solutions possibles dans la plupart des cas est la suivante: 

Disons que vous avez 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 beaucoup de méthodes surchargées, envisagez d'utiliser switch plutôt que des instructions if-else.

( plus de détails )

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

13
Aniket Thakur

Je ne suis pas sûr de la meilleure pratique, 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"
9
chessweb

Je viens d’essayer cela, peut-être que cela répond à 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 masqué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");
6
AntouanK

Puisque JavaScript n’a pas d’option de surcharge de fonction, l’objet peut être utilisé à la place. S'il y a un ou deux arguments obligatoires, il est préférable de les séparer de l'objet options. Voici un exemple d'utilisation d'objet options et de valeurs renseignées avec la valeur par défaut au cas où cette valeur ne serait pas passée 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 + "}";

}

here est un exemple d'utilisation d'objet options

5
Vlad Bezden

Il n’existe aucun moyen de surcharger la fonction en javascript . Je recommande donc de suivre la méthode suivante avec la méthode typeof() au lieu de Plusieurs fonctions pour simuler une 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!

4
Smith Lee

Une autre façon de procéder consiste à utiliser la variable spéciale: arguments , il s’agit d’une implémentation:

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

afin que vous puissiez modifier ce code pour:

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

regarde ça. C'est très cool . http://ejohn.org/blog/javascript-method-overloading/ Astuce Javascript pour vous permettre d'effectuer 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
3
Jaider

Comme ce message contient déjà beaucoup de solutions différentes, je pensais 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.

2
remyH

Vous pouvez désormais surcharger des fonctions dans ECMAScript 2018 sans polyfill, sans vérifier var longueur/type, etc., utilisez simplement la syntaxe spread .

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"

Quelle est la syntaxe répandue?

La proposition Propriétés de repos/propagation pour 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 dans un nouvel objet. Plus sur mdn

Remarque: la syntaxe de propagation 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

2
AlienKevin

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 conserver 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);
  };
}
2
istavros

Surcharge de fonctions par polymorphisme dynamique dans 100 lignes de JS

Cela provient d'un corps de code plus large qui inclut les fonctions de vérification de type isFn, isArr, etc. La version de VanillaJS ci-dessous a été retravaillée pour supprimer toutes les dépendances externes. Toutefois, vous devrez définir vos propres fonctions de vérification de type à utiliser dans les appels .add().

Remarque: Il s'agit d'une fonction à exécution automatique (nous pouvons donc avoir une portée de fermeture/fermeture), d'où l'affectation à window.overload plutô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 ses 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 optionnel de overload() définit la fonction "par défaut" à appeler si la signature ne peut pas être identifiée. Les arguments pour .add() sont:

  1. fn: function définissant la surcharge;
  2. a_vArgumentTests: Array of functions définissant les tests à exécuter sur la arguments. Chaque function accepte un seul argument et renvoie truethy en fonction de la validité de l'argument;
  3. sAlias (Facultatif): string définissant l'alias permettant d'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 autorise en réalité davantage que les surcharges de fonctions traditionnelles, car le deuxième argument a_vArgumentTests de .add() définit en pratique les types personnalisés. Vous pouvez donc créer des arguments non seulement en fonction du type, mais également en fonction de plages, de valeurs ou de collections de valeurs!

Si vous examinez les 145 lignes de code pour overload(), vous constaterez que chaque signature est classée selon le nombre de arguments qui lui ont été transmis. Ceci est fait pour limiter le nombre de tests que nous exécutons. Je surveille également le nombre d'appels. Avec un peu de code supplémentaire, les tableaux de fonctions surchargées pourraient être triés de nouveau afin que les fonctions les plus communément appelées soient testées en premier, ce qui ajoute encore une fois une mesure d'amélioration des performances.

Maintenant, il y a quelques réserves ... Comme Javascript est typé de manière approximative, vous devrez faire attention à votre vArgumentTests car une integer pourrait être validée comme une 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}();
2
Campbeln

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

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

  1. Utilisation du 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 ....   
2
Abdennour TOUMI

La première option mérite vraiment l’attention, car c’est ce que j’ai découvert dans une configuration de code assez complexe. Donc, ma réponse est

  1. Utiliser des noms différents en premier lieu

Avec un indice, mais essentiel, les noms devraient avoir une apparence différente pour l'ordinateur, mais pas pour vous. Nommez les fonctions surchargées telles que: func, func1, func2.

1
alehro

Pour votre cas d'utilisation, voici comment je l'aborderais avec 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 adapter cela pour fonctionner avec n'importe quel nombre de paramètres et simplement modifier vos instructions conditionnelles en conséquence.

1

JavaScript est un langage non typé et je pense seulement que cela a du sens de surcharger une méthode/fonction par rapport au 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...
     }
};
1
Tony

À compter de juillet 2017, voici 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
1
Chong Lip Phang

C’est une vieille question, mais je pense qu’elle nécessite une autre entrée (même si je doute que quelqu'un la lise). L'utilisation des expressions de fonction immédiatement appelées (IIFE) peut être utilisée conjointement avec des fonctions de fermeture et des fonctions en ligne pour permettre une surcharge des fonctions. Prenons l'exemple suivant (artificiel):

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 IIFE crée une portée locale, nous permettant de définir la variable privée old afin de stocker une référence à la définition initiale de la fonction foo. Cette fonction retourne ensuite une fonction inline newFoo qui enregistre le contenu des deux arguments si elle est passée avec exactement deux arguments a et b ou appelle la fonction old si arguments.length !== 2. Ce modèle peut être répété autant de fois que nécessaire pour doter une variable de plusieurs définitions fonctionnelles différentes.

1
wvandaal

Quelque chose comme ceci peut être fait pour surcharger une fonction.

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

0
Supun Kavinda

Je voudrais partager un exemple utile d’approche surchargée.

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: Clair(); // Efface tout le document

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

0

J'utilise cette fonction pour embellir mes surcharges depuis des années:

function overload(){
  const fs = arguments, fallback = fs[fs.length - 1];
  return function(){
    const f = fs[arguments.length] || (arguments.length >= fs.length ? fallback : null);
    return f.apply(this, arguments);
  }
}

Démostré:

function curry1(f){
  return curry2(f, f.length);
}

function curry2(f, minimum){
  return function(...applied){
    if (applied.length >= minimum) {
      return f.apply(this, applied);
    } else {
      return curry2(function(...args){
        return f.apply(this, applied.concat(args));
      }, minimum - applied.length);
    }
  }
}

export const curry = overload(null, curry1, curry2);

Jetez un coup d'œil à la méthode off de jQuery:

  function off( types, selector, fn ) {
    var handleObj, type;
    if ( types && types.preventDefault && types.handleObj ) {

        // ( event )  dispatched jQuery.Event
        handleObj = types.handleObj;
        jQuery( types.delegateTarget ).off(
            handleObj.namespace ?
                handleObj.origType + "." + handleObj.namespace :
                handleObj.origType,
            handleObj.selector,
            handleObj.handler
        );
        return this;
    }
    if ( typeof types === "object" ) {

        // ( types-object [, selector] )
        for ( type in types ) {
            this.off( type, selector, types[ type ] );
        }
        return this;
    }
    if ( selector === false || typeof selector === "function" ) {

        // ( types [, fn] )
        fn = selector;
        selector = undefined;
    }
    if ( fn === false ) {
        fn = returnFalse;
    }
    return this.each( function() {
        jQuery.event.remove( this, types, fn, selector );
    } );
  }

De nombreuses fonctions surchargées, lorsqu'elles sont optimisées pour la performance, sont pratiquement illisibles. Vous devez déchiffrer la tête de la fonction. C'est peut-être plus rapide que d'utiliser une fonction overload comme celle que je fournis; Cependant, il est plus lent, du point de vue humain, de déterminer quelle surcharge a été appelée.

0
Mario

il n'y a pas de surcharge réelle dans JS, mais nous pouvons quand même simuler la surcharge de méthode de plusieurs manières:

méthode n ° 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 n ° 2: utilise les paramètres 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éthode n ° 3: utilisation non définie

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

méthode n ° 4: vérification du type

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

Nous avons fait over.js résoudre ce problème est une manière très élégante. Tu peux faire:

var obj = {

  /**
   * Says something in the console.
   *
   * say(msg) - Says something once.
   * say(msg, times) - Says something many times.
   */
  say: Over(
    function(msg$string){
      console.info(msg$string);
    },
    function(msg$string, times$number){
      for (var i = 0; i < times$number; i++) this.say(msg$string);
    }
  )

};
0
Mat Ryer

J'aime l'approche de @ AntouanK. Je me trouve souvent en train de proposer une fonction avec des nombres ou des paramètres différents. Parfois, ils ne suivent pas un ordre. J'utilise pour cartographier les types de paramètres:

findUDPServers: function(socketProperties, success, error) {
    var fqnMap = [];

    fqnMap['undefined'] = fqnMap['function'] = function(success, error) {
        var socketProperties = {name:'HELLO_SERVER'};

        this.searchServers(socketProperties, success, error);
    };

    fqnMap['object'] = function(socketProperties, success, error) {
        var _socketProperties = _.merge({name:'HELLO_SERVER'}, socketProperties || {});

        this.searchServers(_socketProperties, success, error);
    };

    fqnMap[typeof arguments[0]].apply(this, arguments);
}
0
Andre Medeiros

J'ai donc beaucoup aimé cette façon de faire que j'ai trouvée dans les secrets du ninja javascript

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

vous utilisez ensuite addMethod pour ajouter des fonctions surchargées à n’importe quel objet. La principale confusion dans ce code a été pour moi l'utilisation de fn.length == arguments.length - cela fonctionne car fn.length est le nombre de paramètres attendus, alors que arguments.length est le nombre de paramètres qui sont réellement appelés une fonction. La raison pour laquelle la fonction anonyme n'a pas d'argument est que vous pouvez passer n'importe quel nombre d'arguments en javascript et que le langage pardonne. 

J'ai aimé cela parce que vous pouvez l'utiliser partout - créez simplement cette fonction et utilisez simplement la méthode dans la base de code de votre choix. 

Cela évite également d'avoir une instruction if/switch ridiculement grande, qui devient difficile à lire si vous commencez à écrire du code complexe (la réponse acceptée en résultera).

En ce qui concerne les inconvénients, je suppose que le code est initialement un peu obscur ... mais je ne suis pas sûr des autres? 

0
praks5432

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 des implémentations différentes. Lorsqu'une fonction surchargée est appelée, elle exécutera fonction une implémentation spécifique de cette fonction appropriée au contexte de l'appel. Ce contexte correspond généralement à la quantité d'arguments reçus et permet à un appel de fonction de se comporter différemment en fonction du contexte.

Javascript n'a pas une surcharge de fonctions intégrée. Cependant, ce comportement peut être imité de plusieurs manières. Voici un simple 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 constitué de trois points .... Cela 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);
0