111 votes

Comment surcharger les fonctions en javascript ?

Approche classique (non-js) de la surcharge :

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript ne permet pas de définir plus d'une fonction avec le même nom. C'est pourquoi des choses comme celle-ci apparaissent :

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

Existe-t-il un meilleur moyen de contourner la surcharge de fonctions en javascript que de passer un objet contenant les surcharges ?

Le passage de surcharges peut rapidement rendre une fonction trop verbeuse, car chaque surcharge possible nécessiterait alors une déclaration conditionnelle. L'utilisation de fonctions pour accomplir le //code à l'intérieur de ces déclarations conditionnelles peut provoquer des situations délicates avec les champs d'application.

1 votes

Si vous avez des problèmes avec les surcharges et le champ d'application, vous devriez probablement utiliser un nom de méthode différent, car il semble qu'elle fasse un autre travail.

0 votes

@joshcomley - Les surcharges et la portée nécessitent du code pour les gérer. J'essaie simplement de m'assurer que le code que j'utilise est aussi efficace que possible. Gérer les scopes avec des solutions de contournement, c'est bien, mais je préfère au moins essayer d'utiliser les méthodes des meilleures pratiques.

3 votes

133voto

jfriend00 Points 152127

La surcharge d'arguments en Javascript présente de multiples aspects :

  1. Arguments variables - Vous pouvez passer différents ensembles d'arguments (à la fois en termes de type et de quantité) et la fonction se comportera d'une manière qui correspond aux arguments qui lui sont passés.

  2. Arguments par défaut - Vous pouvez définir une valeur par défaut pour un argument s'il n'est pas passé.

  3. Arguments nommés - L'ordre des arguments n'a plus d'importance et il suffit de nommer les arguments que l'on veut passer à la fonction.

Vous trouverez ci-dessous une section sur chacune de ces catégories de traitement des arguments.

Arguments variables

Étant donné que le javascript n'a pas de vérification de type sur les arguments ou de quantité d'arguments requise, vous pouvez avoir une seule implémentation de myFunc() qui peut s'adapter aux arguments qui lui ont été passés en vérifiant le type, la présence ou la quantité d'arguments.

jQuery fait cela tout le temps. Vous pouvez rendre certains des arguments facultatifs ou vous pouvez brancher votre fonction en fonction des arguments qui lui sont passés.

Pour mettre en œuvre ces types de surcharges, vous pouvez utiliser plusieurs techniques différentes :

  1. Vous pouvez vérifier la présence d'un argument donné en vérifiant si la valeur déclarée du nom de l'argument est undefined .
  2. Vous pouvez vérifier la quantité totale ou les arguments avec arguments.length .
  3. Vous pouvez vérifier le type de tout argument donné.
  4. Pour un nombre variable d'arguments, vous pouvez utiliser la fonction arguments pour accéder à n'importe quel argument donné avec arguments[i] .

Voici quelques exemples :

Examinons la méthode de jQuery. obj.data() méthode. Il prend en charge quatre formes d'utilisation différentes :

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

Chacune d'entre elles déclenche un comportement différent et, sans l'utilisation de cette forme dynamique de surcharge, elle nécessiterait quatre fonctions distinctes.

Voici comment on peut discerner entre toutes ces options en anglais, puis je les combinerai toutes en code :

// get the data element associated with a particular key value
obj.data("key");

Si le premier argument passé à .data() est une chaîne de caractères et le second argument est undefined alors l'appelant doit utiliser ce formulaire.


// set the value associated with a particular key
obj.data("key", value);

Si le deuxième argument n'est pas indéfini, alors définir la valeur d'une clé particulière.


// get all keys/values
obj.data();

Si aucun argument n'est passé, alors il retourne toutes les clés/valeurs dans un objet retourné.


// set all keys/values from the passed in object
obj.data(object);

Si le type du premier argument est un objet simple, alors il faut définir toutes les clés/valeurs de cet objet.


Voici comment vous pourriez combiner tous ces éléments en un seul ensemble de logique javascript :

 // method declaration for .data()
 data: function(key, value) {
     if (arguments.length === 0) {
         // .data()
         // no args passed, return all keys/values in an object
     } else if (typeof key === "string") {
         // first arg is a string, look at type of second arg
         if (typeof value !== "undefined") {
             // .data("key", value)
             // set the value for a particular key
         } else {
             // .data("key")
             // retrieve a value for a key
         }
     } else if (typeof key === "object") {
         // .data(object)
         // set all key/value pairs from this object
     } else {
         // unsupported arguments passed
     }
 },

La clé de cette technique est de s'assurer que toutes les formes d'arguments que vous voulez accepter sont identifiables de manière unique et qu'il n'y a jamais de confusion sur la forme utilisée par l'appelant. Cela nécessite généralement d'ordonner les arguments de manière appropriée et de s'assurer qu'il y a suffisamment d'unicité dans le type et la position des arguments pour que vous puissiez toujours dire quelle forme est utilisée.

Par exemple, si vous avez une fonction qui prend trois arguments de type chaîne :

obj.query("firstArg", "secondArg", "thirdArg");

Vous pouvez facilement rendre le troisième argument optionnel et vous pouvez facilement détecter cette condition, mais vous ne pouvez pas rendre seulement le deuxième argument optionnel parce que vous ne pouvez pas dire lequel de ces arguments l'appelant veut passer parce qu'il n'y a aucun moyen d'identifier si le deuxième argument est censé être le deuxième argument ou si le deuxième argument a été omis, donc ce qui est à la place du deuxième argument est en fait le troisième argument :

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

Comme les trois arguments sont du même type, vous ne pouvez pas faire la différence entre les différents arguments et vous ne savez donc pas ce que l'appelant voulait. Avec ce style d'appel, seul le troisième argument peut être facultatif. Si vous vouliez omettre le deuxième argument, il devrait être passé en tant que null (ou une autre valeur détectable) à la place et votre code le détectera :

obj.query("firstArg", null, "thirdArg");

Voici un exemple jQuery d'arguments optionnels. Les deux arguments sont optionnels et prennent des valeurs par défaut s'ils ne sont pas passés :

clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
},

Voici un exemple jQuery où l'argument peut être absent ou de l'un des trois types différents, ce qui vous donne quatre surcharges différentes :

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
            this[0].innerHTML.replace(rinlinejQuery, "") :
            null;

    // See if we can take a shortcut and just use innerHTML
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, "<$1></$2>");

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
                if ( this[i].nodeType === 1 ) {
                    jQuery.cleanData( this[i].getElementsByTagName("*") );
                    this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } catch(e) {
            this.empty().append( value );
        }

    } else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery( this );

            self.html( value.call(this, i, self.html()) );
        });

    } else {
        this.empty().append( value );
    }

    return this;
},

Arguments nommés

D'autres langages (comme Python) permettent de passer des arguments nommés, ce qui permet de ne passer que certains arguments et de rendre les arguments indépendants de l'ordre dans lequel ils sont passés. Javascript ne prend pas directement en charge la fonctionnalité des arguments nommés. Un modèle de conception couramment utilisé à sa place consiste à transmettre une carte de propriétés/valeurs. Cela peut être fait en passant un objet avec des propriétés et des valeurs ou dans ES6 et plus, vous pouvez passer un objet Map lui-même.

Voici un exemple simple en ES5 :

La méthode de jQuery $.ajax() accepte une forme d'utilisation où vous lui passez un seul paramètre qui est un objet Javascript ordinaire avec des propriétés et des valeurs. Les propriétés que vous lui passez déterminent les arguments/options qui sont passés à l'appel ajax. Certains sont obligatoires, beaucoup sont optionnels. Comme il s'agit de propriétés d'un objet, il n'y a pas d'ordre spécifique. En fait, il y a plus de 30 propriétés différentes qui peuvent être transmises sur cet objet, une seule (l'url) est obligatoire.

Voici un exemple :

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
    // process result here
});

Intérieur de la $.ajax() il peut alors simplement interroger les propriétés qui ont été passées sur l'objet entrant et les utiliser comme arguments nommés. Cela peut être fait soit avec for (prop in obj) ou en récupérant toutes les propriétés dans un tableau avec Object.keys(obj) et ensuite itérer ce tableau.

Cette technique est utilisée très couramment en Javascript lorsqu'il y a un grand nombre d'arguments et/ou que de nombreux arguments sont facultatifs. Remarque : il incombe à la fonction d'implémentation de s'assurer qu'un ensemble minimal d'arguments valides est présent et de donner à l'appelant des informations de débogage sur ce qui manque si des arguments insuffisants sont passés (probablement en lançant une exception avec un message d'erreur utile).

Dans un environnement ES6, il est possible d'utiliser la déstructuration pour créer des propriétés/valeurs par défaut pour l'objet passé ci-dessus. Ce point est abordé plus en détail dans cet article de référence .

Voici un exemple tiré de cet article :

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};

Ensuite, vous pouvez l'appeler comme n'importe lequel de ceux-ci :

selectEntries({start: 5});
selectEntries({start: 5, end: 10});
selectEntries({start: 5, end: 10, step: 2});
selectEntries({step: 3});
selectEntries();

Les arguments que vous ne mentionnez pas dans l'appel de la fonction prendront leur valeur par défaut dans la déclaration de la fonction.

Cela crée des propriétés et des valeurs par défaut pour le start , end y step sur un objet passé à la fonction selectEntries() fonction.

Valeurs par défaut des arguments de fonction

Dans l'ES6, Javascript ajoute un support linguistique intégré pour les valeurs par défaut des arguments.

Par exemple :

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

Description plus détaillée des possibilités d'utilisation ici sur MDN .

0 votes

Je devrais donc m'en tenir à une fonction adaptable et l'accepter comme la meilleure pratique pour mettre en œuvre la façade de surcharge de fonction ?

2 votes

@TravisJ - oui, la fonction adaptable est la façon dont les surcharges sont effectuées en Javascript. Votre autre option est d'utiliser une fonction nommée séparément avec des arguments différents. Si les deux implémentations n'ont rien en commun, l'une ou l'autre est une pratique acceptable. Si les deux implémentations font essentiellement la même chose, mais commencent simplement avec des arguments différents, alors je pense que l'utilisation de la fonction adaptable rend votre interface plus compacte et vous donne l'opportunité de partager du code entre les deux implémentations liées.

0 votes

Ajout de quelques exemples de code, expliqués à la fois en anglais et en code réel.

33voto

zzzzBov Points 62084

La surcharge d'une fonction en JavaScript peut se faire de plusieurs façons. Toutes impliquent une seule fonction maîtresse qui effectue tous les processus ou qui délègue à des sous-fonctions/processus.

L'une des techniques les plus courantes consiste à utiliser un simple interrupteur :

function foo(a, b) {
    switch (arguments.length) {
    case 0:
        //do basic code
        break;
    case 1:
        //do code with `a`
        break;
    case 2:
    default:
        //do code with `a` & `b`
        break;
    }
}

Une technique plus élégante consisterait à utiliser un tableau (ou un objet si vous ne créez pas de surcharges pour les fonctions chaque le nombre d'arguments) :

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

Cet exemple précédent n'est pas très élégant, n'importe qui pourrait modifier fooArr et il échouerait si quelqu'un transmet plus de 2 arguments à foo Il serait donc préférable d'utiliser un modèle de module et de procéder à quelques vérifications :

var foo = (function () {
    var fns;
    fns = [
        function () {
        },
        function (a) {
        },
        function (a, b) {
        }
    ];
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = foo.length;
        }
        return fns[fnIndex].call(this, a, b);
    }
    return foo;
}());

Bien sûr, vos surcharges pourraient vouloir utiliser un nombre dynamique de paramètres, et vous pourriez donc utiliser un objet pour le paramètre fns collection.

var foo = (function () {
    var fns;
    fns = {};
    fns[0] = function () {
    };
    fns[1] = function (a) {
    };
    fns[2] = function (a, b) {
    };
    fns.params = function (a, b /*, params */) {
    };
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = 'params';
        }
        return fns[fnIndex].apply(this, Array.prototype.slice.call(arguments));
    }
    return foo;
}());

Ma préférence personnelle va à la switch bien qu'il renforce la fonction de maître. Un exemple courant d'utilisation de cette technique serait une méthode accesseur/mutateur :

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}

0 votes

C'est un bel exemple d'alternative :)

1 votes

Joli, j'aime cette 2ème technique

4voto

Valentin Rusk Points 474

J'utilise une approche de surcharge un peu différente basée sur le nombre d'arguments. Cependant, je crois que l'approche de John Fawcett est également bonne. Voici l'exemple, code basé sur les explications de John Resig (auteur de jQuery).

// o = existing object, n = function name, f = function.
    function overload(o, n, f){
        var old = o[n];
        o[n] = function(){
            if(f.length == arguments.length){
                return f.apply(this, arguments);
            }
            else if(typeof o == 'function'){
                return old.apply(this, arguments);
            }
        };
    }

la facilité d'utilisation :

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)

4voto

Murtnowski Points 1190

En javascript, vous pouvez implémenter la fonction une seule fois et l'invoquer sans les paramètres. myFunc() Vous vérifiez ensuite si les options sont "indéfinies".

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}

3voto

John Fawcett Points 175

https://github.com/jrf0110/leFunc

var getItems = leFunc({
  "string": function(id){
    // Do something
  },
  "string,object": function(id, options){
    // Do something else
  },
  "string,object,function": function(id, options, callback){
    // Do something different
    callback();
  },
  "object,string,function": function(options, message, callback){
    // Do something ca-raaaaazzzy
    callback();
  }
});

getItems("123abc"); // Calls the first function - "string"
getItems("123abc", {poop: true}); // Calls the second function - "string,object"
getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X