Behandlung optionaler Parameter in Javascript

127

Ich habe eine statische Javascript-Funktion, die 1, 2 oder 3 Parameter annehmen kann:

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

Ich weiß, dass ich immer testen kann, ob ein bestimmter Parameter undefiniert ist, aber wie würde ich wissen, ob der Parameter oder der Rückruf übergeben wurde?

Was ist der beste Weg, dies zu tun?


Beispiele für das, was übergeben werden könnte:

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);
jd.
quelle
2
Können Sie ein Beispiel dafür zeigen, was übergeben werden könnte?
James Black

Antworten:

163

Sie können wissen, wie viele Argumente an Ihre Funktion übergeben wurden, und Sie können überprüfen, ob Ihr zweites Argument eine Funktion ist oder nicht:

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

Sie können das Argumentobjekt auch folgendermaßen verwenden:

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

Wenn Sie interessiert sind, lesen Sie diesen Artikel von John Resig über eine Technik zur Simulation der Methodenüberladung in JavaScript.

CMS
quelle
Warum Object.prototype.toString.call (Parameter) == "[Objektfunktion]" und nicht typeof (Parameter) === 'Funktion' verwenden? Gibt es einen wichtigen Unterschied zwischen ihnen? PS Der Artikel, den Sie erwähnen, scheint letzteres zu verwenden
Tomer Cagan
@TomerCagan Ich denke, es ist eine Frage der Präferenz (ish). Unter dieser Frage gibt es einige gute Antworten / Kommentare zum Thema .
Philiiiiiipp
75

Äh - das würde bedeuten, dass Sie Ihre Funktion mit Argumenten aufrufen, die nicht in der richtigen Reihenfolge sind ... was ich nicht empfehlen würde.

Ich würde empfehlen, stattdessen ein Objekt wie folgt in Ihre Funktion einzuspeisen:

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

Leistungen:

  • Sie müssen die Reihenfolge der Argumente nicht berücksichtigen
  • Sie müssen keine Typprüfung durchführen
  • Es ist einfacher, Standardwerte zu definieren, da keine Typprüfung erforderlich ist
  • weniger Kopfschmerzen. Stellen Sie sich vor, wenn Sie ein viertes Argument hinzufügen, müssten Sie Ihre Typprüfung jedes Mal aktualisieren, und was ist, wenn das vierte oder aufeinanderfolgende auch Funktionen sind?

Nachteile:

  • Zeit, den Code umzugestalten

Wenn Sie keine Wahl haben, können Sie mithilfe einer Funktion erkennen, ob ein Objekt tatsächlich eine Funktion ist (siehe letztes Beispiel).

Hinweis: Dies ist der richtige Weg, um eine Funktion zu erkennen:

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

isFunction( function(){} )
meder omuraliev
quelle
"Dies würde aufgrund einiger ES-Fehler in 99% der Fälle funktionieren." Kannst du mehr erklären? Warum könnte es schief gehen?
jd.
Ich habe den richtigen Code hinzugefügt, um eine Funktion zu erkennen. Ich glaube, der Fehler ist hier: bugs.ecmascript.org/ticket/251
meder omuraliev
Ich würde Ihnen dringend empfehlen, nur ein Objekt zu füttern. Wenn nicht, verwenden Sie die CMS-Methode.
Meder Omuraliev
Oh ... verdammt ... habe gerade die gleiche Idee gepostet.
Arnis Lapsa
1
Ein weiterer möglicher Nachteil ist der Mangel an Intelligenz. Ich halte das nicht für eine große Sache, sollte aber beachtet werden.
Edyn
2

Sie sollten den Typ der empfangenen Parameter überprüfen. Vielleicht sollten Sie ein argumentsArray verwenden, da der zweite Parameter manchmal "Parameter" und manchmal "Rückruf" sein kann und die Benennung der Parameter irreführend sein kann.

Kamil Szot
quelle
2

Ich weiß, dass dies eine ziemlich alte Frage ist, aber ich habe mich kürzlich damit befasst. Lassen Sie mich wissen, was Sie von dieser Lösung halten.

Ich habe ein Dienstprogramm erstellt, mit dem ich Argumente stark eingeben und optional lassen kann. Sie verpacken Ihre Funktion grundsätzlich in einen Proxy. Wenn Sie ein Argument überspringen, ist es undefiniert . Es kann eigenartig werden, wenn Sie mehrere optionale Argumente mit demselben Typ direkt nebeneinander haben. (Es gibt Optionen zum Übergeben von Funktionen anstelle von Typen, um benutzerdefinierte Argumentprüfungen durchzuführen und Standardwerte für jeden Parameter anzugeben.)

So sieht die Implementierung aus:

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

Zur Verdeutlichung ist hier was los:

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

           }
         );
};

Sie können den arrangArgs-Proxy-Code in meinem GitHub-Repository hier anzeigen:

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

Hier ist die Utility-Funktion mit einigen Kommentaren, die aus dem Repository kopiert wurden:

/*
 ****** 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();
}
joelvh
quelle
2

Ich empfehle Ihnen, ArgueJS zu verwenden .

Sie können Ihre Funktion einfach folgendermaßen eingeben:

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

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

Ich habe anhand Ihrer Beispiele berücksichtigt, dass Ihr idParameter eine Zeichenfolge sein soll, oder? Jetzt getDataist ein erforderlich String idund akzeptiert die Optionen Object parametersund Function callback. Alle von Ihnen veröffentlichten Anwendungsfälle funktionieren wie erwartet.

zVictor
quelle
1

Wollen Sie damit sagen, dass Sie Aufrufe wie diese haben können: getData (id, parameters); getData (ID, Rückruf)?

In diesem Fall können Sie sich offensichtlich nicht auf die Position verlassen und müssen sich auf die Analyse des Typs verlassen: getType () und gegebenenfalls getTypeName ()

Überprüfen Sie, ob der betreffende Parameter ein Array oder eine Funktion ist.

DmitryK
quelle
0

Ich denke, Sie möchten hier typeof () verwenden:

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"
Mark Bessey
quelle
0

Wenn Ihr Problem nur mit Funktion Überlastung ist (Sie müssen überprüfen , ob ‚Parameter‘ parameter ‚Parameter‘ und nicht ‚Rückruf‘ ist), würde ich empfehlen Sie nicht über Argumenttyp nicht die Mühe und
verwenden diesen Ansatz . Die Idee ist einfach: Verwenden Sie Literalobjekte, um Ihre Parameter zu kombinieren:

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!");}
});
Arnis Lapsa
quelle
0

Dies kann ein selbsterklärendes Beispiel sein:

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);
}
ses
quelle
-5

Können Sie die Funktion überschreiben? Wird das nicht funktionieren:

function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}
J. Hendrix
quelle
8
Nein, das wird nicht funktionieren. Sie erhalten keine Fehler, aber Javascript verwendet immer die neueste Funktion, die Sie definiert haben.
jd.
6
Beeindruckend. Ich dachte du wärst verrückt. Ich habe es gerade getestet. Du hast recht. Meine Welt hat sich für mich nur ein bisschen verändert. Ich glaube, ich muss mir heute viel JavaScript ansehen, um sicherzustellen, dass ich so etwas nicht in der Produktion habe. Danke für den Kommentar. Ich habe dir eine +1 gegeben.
J. Hendrix