Gestion des paramètres facultatifs en javascript


127

J'ai une fonction javascript statique qui peut prendre 1, 2 ou 3 paramètres:

function getData(id, parameters, callback) //parameters (associative array) and callback (function) are optional

Je sais que je peux toujours tester si un paramètre donné n'est pas défini, mais comment savoir si ce qui a été passé était le paramètre ou le rappel?

Quelle est la meilleure façon de procéder?


Exemples de ce qui pourrait être transmis:

1:

getData('offers');

2:

var array = new Array();
array['type']='lalal';
getData('offers',array);

3:

var foo = function (){...}
getData('offers',foo);

4:

getData('offers',array,foo);

2
Pouvez-vous montrer un exemple de ce qui pourrait être transmis?
James Black

Réponses:


163

Vous pouvez savoir combien d' arguments ont été passés à votre fonction et vous pouvez vérifier si votre deuxième argument est une fonction ou non:

function getData (id, parameters, callback) {
  if (arguments.length == 2) { // if only two arguments were supplied
    if (Object.prototype.toString.call(parameters) == "[object Function]") {
      callback = parameters; 
    }
  }
  //...
}

Vous pouvez également utiliser l'objet arguments de cette manière:

function getData (/*id, parameters, callback*/) {
  var id = arguments[0], parameters, callback;

  if (arguments.length == 2) { // only two arguments supplied
    if (Object.prototype.toString.call(arguments[1]) == "[object Function]") {
      callback = arguments[1]; // if is a function, set as 'callback'
    } else {
      parameters = arguments[1]; // if not a function, set as 'parameters'
    }
  } else if (arguments.length == 3) { // three arguments supplied
      parameters = arguments[1];
      callback = arguments[2];
  }
  //...
}

Si vous êtes intéressé, jetez un œil à cet article de John Resig, sur une technique pour simuler la surcharge de méthodes sur JavaScript.


Pourquoi utiliser Object.prototype.toString.call (parameters) == "[object Function]" et non typeof (parameters) === 'function'? Y a-t-il une différence importante entre eux? PS l'article que vous mentionnez semble utiliser ce dernier
Tomer Cagan

@TomerCagan Je pense que c'est une question de préférence (ish). Il y a quelques bonnes réponses / commentaires sur le sujet sous cette question.
Philiiiiiipp

75

Euh - cela impliquerait que vous invoquez votre fonction avec des arguments qui ne sont pas dans le bon ordre ... ce que je ne recommanderais pas.

Je recommanderais plutôt d'alimenter un objet dans votre fonction comme ceci:

function getData( props ) {
    props = props || {};
    props.params = props.params || {};
    props.id = props.id || 1;
    props.callback = props.callback || function(){};
    alert( props.callback )
};

getData( {
    id: 3,
    callback: function(){ alert('hi'); }
} );

Avantages:

  • vous n'avez pas à tenir compte de l'ordre des arguments
  • vous n'avez pas à faire de vérification de type
  • il est plus facile de définir des valeurs par défaut car aucune vérification de type n'est nécessaire
  • moins de maux de tête. imaginez que si vous ajoutiez un quatrième argument, vous devriez mettre à jour votre vérification de type à chaque fois, et que se passe-t-il si le quatrième ou consécutif sont également des fonctions?

Désavantages:

  • il est temps de refactoriser le code

Si vous n'avez pas le choix, vous pouvez utiliser une fonction pour détecter si un objet est bien une fonction (voir le dernier exemple).

Remarque: c'est la bonne façon de détecter une fonction:

function isFunction(obj) {
    return Object.prototype.toString.call(obj) === "[object Function]";
}

isFunction( function(){} )

"cela fonctionnerait 99% du temps en raison de certains bogues ES." Pouvez-vous expliquer plus? Pourquoi cela pourrait-il mal tourner?
jd.

J'ai ajouté le bon code pour détecter une fonction. Je crois que le bogue est là: bugs.ecmascript.org/ticket/251
meder omuraliev

Je vous suggère fortement de ne nourrir qu'un seul objet. Sinon, utilisez la méthode de CMS.
meder omuraliev

Oh ... putain ... Je viens de publier la même idée.
Arnis Lapsa

1
Un autre inconvénient possible est le manque d'intellisense. Je ne considère pas cela comme un gros problème, mais il faut le noter.
Edyn


2

Vous devez vérifier le type de paramètres reçus. Peut-être devriez-vous utiliser argumentsarray car le second paramètre peut parfois être des «paramètres» et parfois «callback» et le nommer paramètres peut être trompeur.


2

Je sais que c'est une question assez ancienne, mais j'en ai traité récemment. Faites-moi savoir ce que vous pensez de cette solution.

J'ai créé un utilitaire qui me permet de taper fortement des arguments et de les laisser facultatifs. Vous enveloppez essentiellement votre fonction dans un proxy. Si vous ignorez un argument, il n'est pas défini . Cela peut devenir bizarre si vous avez plusieurs arguments optionnels avec le même type juste à côté de l'autre. (Il existe des options pour passer des fonctions au lieu de types pour effectuer des vérifications d'argument personnalisées, ainsi que pour spécifier des valeurs par défaut pour chaque paramètre.)

Voici à quoi ressemble l'implémentation:

function displayOverlay(/*message, timeout, callback*/) {
  return arrangeArgs(arguments, String, Number, Function, 
    function(message, timeout, callback) {
      /* ... your code ... */
    });
};

Pour plus de clarté, voici ce qui se passe:

function displayOverlay(/*message, timeout, callback*/) {
  //arrangeArgs is the proxy
  return arrangeArgs(
           //first pass in the original arguments
           arguments, 
           //then pass in the type for each argument
           String,  Number,  Function, 
           //lastly, pass in your function and the proxy will do the rest!
           function(message, timeout, callback) {

             //debug output of each argument to verify it's working
             console.log("message", message, "timeout", timeout, "callback", callback);

             /* ... your code ... */

           }
         );
};

Vous pouvez afficher le code proxy arrangeArgs dans mon référentiel GitHub ici:

https://github.com/joelvh/Sysmo.js/blob/master/sysmo.js

Voici la fonction utilitaire avec quelques commentaires copiés depuis le référentiel:

/*
 ****** Overview ******
 * 
 * Strongly type a function's arguments to allow for any arguments to be optional.
 * 
 * Other resources:
 * http://ejohn.org/blog/javascript-method-overloading/
 * 
 ****** Example implementation ******
 * 
 * //all args are optional... will display overlay with default settings
 * var displayOverlay = function() {
 *   return Sysmo.optionalArgs(arguments, 
 *            String, [Number, false, 0], Function, 
 *            function(message, timeout, callback) {
 *              var overlay = new Overlay(message);
 *              overlay.timeout = timeout;
 *              overlay.display({onDisplayed: callback});
 *            });
 * }
 * 
 ****** Example function call ******
 * 
 * //the window.alert() function is the callback, message and timeout are not defined.
 * displayOverlay(alert);
 * 
 * //displays the overlay after 500 miliseconds, then alerts... message is not defined.
 * displayOverlay(500, alert);
 * 
 ****** Setup ******
 * 
 * arguments = the original arguments to the function defined in your javascript API.
 * config = describe the argument type
 *  - Class - specify the type (e.g. String, Number, Function, Array) 
 *  - [Class/function, boolean, default] - pass an array where the first value is a class or a function...
 *                                         The "boolean" indicates if the first value should be treated as a function.
 *                                         The "default" is an optional default value to use instead of undefined.
 * 
 */
arrangeArgs: function (/* arguments, config1 [, config2] , callback */) {
  //config format: [String, false, ''], [Number, false, 0], [Function, false, function(){}]
  //config doesn't need a default value.
  //config can also be classes instead of an array if not required and no default value.

  var configs = Sysmo.makeArray(arguments),
      values = Sysmo.makeArray(configs.shift()),
      callback = configs.pop(),
      args = [],
      done = function() {
        //add the proper number of arguments before adding remaining values
        if (!args.length) {
          args = Array(configs.length);
        }
        //fire callback with args and remaining values concatenated
        return callback.apply(null, args.concat(values));
      };

  //if there are not values to process, just fire callback
  if (!values.length) {
    return done();
  }

  //loop through configs to create more easily readable objects
  for (var i = 0; i < configs.length; i++) {

    var config = configs[i];

    //make sure there's a value
    if (values.length) {

      //type or validator function
      var fn = config[0] || config,
          //if config[1] is true, use fn as validator, 
          //otherwise create a validator from a closure to preserve fn for later use
          validate = (config[1]) ? fn : function(value) {
            return value.constructor === fn;
          };

      //see if arg value matches config
      if (validate(values[0])) {
        args.push(values.shift());
        continue;
      }
    }

    //add a default value if there is no value in the original args
    //or if the type didn't match
    args.push(config[2]);
  }

  return done();
}

2

Je vous recommande d'utiliser ArgueJS .

Vous pouvez simplement taper votre fonction de cette façon:

function getData(){
  arguments = __({id: String, parameters: [Object], callback: [Function]})

  // and now access your arguments by arguments.id,
  //          arguments.parameters and arguments.callback
}

J'ai considéré par vos exemples que vous voulez que votre idparamètre soit une chaîne, non? Maintenant, getDatanécessite un String idet accepte les options Object parameterset Function callback. Tous les cas d'utilisation que vous avez publiés fonctionneront comme prévu.



1

Êtes-vous en train de dire que vous pouvez avoir des appels comme ceux-ci: getData (id, paramètres); getData (id, rappel)?

Dans ce cas, vous ne pouvez évidemment pas vous fier à la position et vous devez vous fier à l'analyse du type: getType () puis si nécessaire getTypeName ()

Vérifiez si le paramètre en question est un tableau ou une fonction.


0

Je pense que vous voulez utiliser typeof () ici:

function f(id, parameters, callback) {
  console.log(typeof(parameters)+" "+typeof(callback));
}

f("hi", {"a":"boo"}, f); //prints "object function"
f("hi", f, {"a":"boo"}); //prints "function object"

0

Si votre problème concerne uniquement la surcharge de fonctions (vous devez vérifier si le paramètre 'parameters' est 'parameters' et non 'callback'), je vous recommande de ne pas vous soucier du type d'argument et d'
utiliser cette approche . L'idée est simple - utilisez des objets littéraux pour combiner vos paramètres:

function getData(id, opt){
    var data = voodooMagic(id, opt.parameters);
    if (opt.callback!=undefined)
      opt.callback.call(data);
    return data;         
}

getData(5, {parameters: "1,2,3", callback: 
    function(){for (i=0;i<=1;i--)alert("FAIL!");}
});

0

Cet exemple, je suppose, peut être explicite:

function clickOn(elem /*bubble, cancelable*/) {
    var bubble =     (arguments.length > 1)  ? arguments[1] : true;
    var cancelable = (arguments.length == 3) ? arguments[2] : true;

    var cle = document.createEvent("MouseEvent");
    cle.initEvent("click", bubble, cancelable);
    elem.dispatchEvent(cle);
}

-5

Pouvez-vous remplacer la fonction? Cela ne fonctionnera-t-il pas:

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}

8
Non, cela ne fonctionnera pas. Vous n'obtiendrez aucune erreur mais Javascript utilisera toujours la dernière fonction que vous avez définie.
jd.

6
Sensationnel. Je pensais que tu étais fou. Je viens de le tester. Vous avez raison. Mon monde vient de changer un peu pour moi. Je pense que j'ai beaucoup de JavaScript à regarder aujourd'hui pour m'assurer que je n'ai rien de tel en production. Merci pour le commentaire. Je vous ai donné un +1.
J.Hendrix
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.