Gestione dei parametri opzionali in javascript


127

Ho una funzione javascript statica che può accettare 1, 2 o 3 parametri:

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

So che posso sempre verificare se un determinato parametro non è definito, ma come faccio a sapere se ciò che è stato passato era il parametro o il callback?

Qual è il modo migliore per farlo?


Esempi di cosa potrebbe essere passato:

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
Puoi mostrare un esempio di cosa potrebbe essere passato?
James Black,

Risposte:


163

Puoi sapere quanti argomenti sono stati passati alla tua funzione e puoi verificare se il tuo secondo argomento è una funzione o meno:

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

È inoltre possibile utilizzare l'oggetto argomenti in questo modo:

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];
  }
  //...
}

Se sei interessato, dai un'occhiata a questo articolo di John Resig, su una tecnica per simulare il sovraccarico dei metodi su JavaScript.


Perché usare Object.prototype.toString.call (parametri) == "[Funzione oggetto]" e non digitareof (parametri) === 'funzione'? C'è qualche differenza importante tra loro? PS l'articolo che hai citato sembra usare quest'ultimo
Tomer Cagan,

@TomerCagan Penso che sia una questione di preferenza (ish). Ci sono alcune buone risposte / commenti sull'argomento sotto questa domanda.
Philiiiiiipp,

75

Ehm - ciò implicherebbe che stai invocando la tua funzione con argomenti che non sono nell'ordine corretto ... che non consiglierei.

Raccomanderei invece di alimentare un oggetto con la tua funzione in questo modo:

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

Benefici:

  • non è necessario tenere conto dell'ordine degli argomenti
  • non devi fare il controllo del tipo
  • è più facile definire i valori predefiniti perché non è necessario il controllo del tipo
  • meno mal di testa. immagina che se hai aggiunto un quarto argomento, dovresti aggiornare il tuo tipo di controllo ogni volta, e cosa succede se anche il quarto o il successivo sono funzioni?

svantaggi:

  • tempo di refactoring del codice

Se non hai scelta, puoi utilizzare una funzione per rilevare se un oggetto è effettivamente una funzione (vedi l'ultimo esempio).

Nota: questo è il modo corretto di rilevare una funzione:

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

isFunction( function(){} )

"questo funzionerebbe il 99% delle volte a causa di alcuni bug ES". Puoi spiegare di più? Perché potrebbe andare storto?
jd.

Ho aggiunto il codice corretto per rilevare una funzione. Credo che il bug sia qui: bugs.ecmascript.org/ticket/251
meder omuraliev

Consiglio vivamente di alimentare solo un oggetto. In caso contrario, utilizzare il metodo CMS.
meder omuraliev,

Oh ... dannazione ... Ho appena pubblicato la stessa idea.
Arnis Lapsa,

1
Un altro possibile svantaggio è la mancanza di intellisenso. Non lo considero un grosso problema, ma va notato.
Edyn,


2

È necessario verificare il tipo di parametri ricevuti. Forse dovresti usare l' argumentsarray poiché il secondo parametro a volte può essere "parametri" e talvolta "callback" e nominarlo potrebbe essere fuorviante.


2

So che questa è una domanda piuttosto vecchia, ma mi sono occupato di questo di recente. Fammi sapere cosa ne pensi di questa soluzione.

Ho creato un'utilità che mi consente di digitare con forza argomenti e di renderli opzionali. Fondamentalmente avvolgi la tua funzione in un proxy. Se salti un argomento, non è definito . Può diventare strano se hai più argomenti opzionali con lo stesso tipo uno accanto all'altro. (Esistono opzioni per passare funzioni anziché tipi per eseguire controlli di argomenti personalizzati, oltre a specificare i valori predefiniti per ciascun parametro.)

Ecco come appare l'implementazione:

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

Per chiarezza, ecco cosa sta succedendo:

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 ... */

           }
         );
};

È possibile visualizzare il codice proxy arrangArgs nel mio repository GitHub qui:

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

Ecco la funzione di utilità con alcuni commenti copiati dal repository:

/*
 ****** 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

Ti consiglio di usare ArgueJS .

Puoi semplicemente digitare la tua funzione in questo modo:

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

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

Ho considerato dai tuoi esempi che vuoi che il tuo idparametro sia una stringa, giusto? Ora, getDatarichiede un String ide accetta gli opzionali Object parameterse Function callback. Tutti i casi d'uso che hai pubblicato funzioneranno come previsto.



1

Stai dicendo che puoi avere chiamate come queste: getData (id, parametri); getData (id, callback)?

In questo caso non puoi ovviamente fare affidamento sulla posizione e devi fare affidamento sull'analisi del tipo: getType () e quindi, se necessario, getTypeName ()

Controllare se il parametro in questione è un array o una funzione.


0

Penso che tu voglia usare typeof () qui:

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

Se il tuo problema è solo con il sovraccarico della funzione (devi verificare se il parametro "parametri" è "parametri" e non "richiamata"), ti consiglio di non preoccuparti del tipo di argomento e
utilizzare questo approccio . L'idea è semplice: usa oggetti letterali per combinare i tuoi parametri:

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

Immagino che questo possa essere un esempio esplicativo:

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

Puoi ignorare la funzione? Questo non funzionerà:

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

8
No, questo non funzionerà. Non otterrai alcun errore ma Javascript utilizzerà sempre l'ultima funzione che hai definito.
jd.

6
Wow. Pensavo fossi pazzo. L'ho appena provato. Hai ragione. Il mio mondo è appena cambiato un po 'per me. Penso di avere molto JavaScript da guardare oggi per essere sicuro di non avere nulla di simile in produzione. Grazie per il commento. Ti ho dato un +1.
J.Hendrix,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.