Objekt mit mehreren Argumenten und Optionen

157

Wenn ich eine JavaScript-Funktion mit mehreren Argumenten erstelle, bin ich immer mit dieser Auswahl konfrontiert: Übergeben einer Liste von Argumenten vs. Übergeben eines Optionsobjekts.

Zum Beispiel schreibe ich eine Funktion, um eine Knotenliste einem Array zuzuordnen:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

Ich könnte stattdessen folgendes verwenden:

function map(options){
    ...
}

Dabei ist Optionen ein Objekt:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

Welcher ist der empfohlene Weg? Gibt es Richtlinien, wann einer gegen den anderen verwendet werden soll?

[Update] Es scheint einen Konsens zugunsten des Optionsobjekts zu geben, daher möchte ich einen Kommentar hinzufügen: Ein Grund, warum ich versucht war, die Liste der Argumente in meinem Fall zu verwenden, war ein Verhalten, das mit dem JavaScript übereinstimmt eingebaute array.map Methode.

Christophe
quelle
2
Die zweite Option gibt Ihnen benannte Argumente, was meiner Meinung nach eine nette Sache ist.
Werner Kvalem Vesterås
Sind sie optionale oder erforderliche Argumente?
Ich hasse faul
@ user1689607 In meinem Beispiel sind die letzten drei optional.
Christophe
Da Ihre letzten beiden Argumente sehr ähnlich sind, können Sie, wenn der Benutzer nur das eine oder das andere übergeben hat, nie wirklich wissen, welches beabsichtigt war. Aus diesem Grund würden Sie fast benannte Argumente benötigen. Ich kann jedoch zu schätzen wissen, dass Sie eine API beibehalten möchten, die der nativen API ähnelt.
Ich hasse Lazy
1
Das Modellieren nach der nativen API ist keine schlechte Sache, wenn Ihre Funktion etwas Ähnliches tut. Es kommt alles darauf an, "was den Code am besten lesbar macht". Array.prototype.maphat eine einfache API, die keinen halb erfahrenen Programmierer verwirren sollte.
Jeremy J Starcher

Antworten:

160

Wie viele andere ziehe ich es oft vor, ein options objectan eine Funktion zu übergeben, anstatt eine lange Liste von Parametern zu übergeben, aber es hängt wirklich vom genauen Kontext ab.

Ich benutze die Lesbarkeit von Code als Lackmustest.

Zum Beispiel, wenn ich diesen Funktionsaufruf habe:

checkStringLength(inputStr, 10);

Ich denke, dass Code so wie er ist gut lesbar ist und das Übergeben einzelner Parameter in Ordnung ist.

Auf der anderen Seite gibt es Funktionen mit Aufrufen wie diesen:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Völlig unlesbar, es sei denn, Sie recherchieren. Auf der anderen Seite liest sich dieser Code gut:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

Es ist eher eine Kunst als eine Wissenschaft, aber wenn ich Faustregeln nennen müsste:

Verwenden Sie einen Optionsparameter, wenn:

  • Sie haben mehr als vier Parameter
  • Alle Parameter sind optional
  • Sie mussten die Funktion schon einmal nachschlagen, um herauszufinden, welche Parameter erforderlich sind
  • Wenn jemand jemals versucht, Sie zu erwürgen, während er "ARRRRRG!"
Jeremy J Starcher
quelle
10
Gute Antwort. Es hängt davon ab, ob. Vorsicht vor booleschen Fallen ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon
2
Oh ja ... ich hatte diesen Link vergessen. Es hat mich wirklich dazu gebracht, die Funktionsweise von APIs zu überdenken, und ich habe sogar einige Codeteile neu geschrieben, nachdem ich erfahren hatte, dass ich Dinge dumm gemacht habe. Vielen Dank!
Jeremy J Starcher
1
"Sie mussten jemals die Funktion nachschlagen, um herauszufinden, welche Parameter erforderlich sind " - Wenn Sie stattdessen ein Optionsobjekt verwenden, müssen Sie nicht immer nach der Methode suchen, um herauszufinden, welche Schlüssel benötigt werden und wie sie heißen. Intellisense in der IDE zeigt diese Informationen nicht an, während dies bei Parametern der Fall ist. In den meisten IDEs können Sie einfach mit der Maus über die Methode fahren und es werden Ihnen die Parameter angezeigt.
Simbolo
1
Wenn Sie an den Leistungsfolgen dieser Best Practices für die Codierung interessiert sind, finden Sie hier einen jsPerf-Test in beide Richtungen: jsperf.com/function-boolean-arguments-vs-options-object/10 Beachten Sie die kleine Wendung in der dritte Alternative, bei der ich ein 'voreingestelltes' (konstantes) Optionsobjekt verwende, das ausgeführt werden kann, wenn Sie viele Aufrufe (während der Laufzeit Ihrer Laufzeit, z. B. Ihrer Webseite) mit denselben Einstellungen haben, die zur Entwicklungszeit bekannt sind (kurz gesagt) : wenn Ihre Optionswerte in Ihrem Quellcode fest codiert sind).
Ger Hobbelt
2
@ Sean Ehrlich gesagt verwende ich diesen Codierungsstil überhaupt nicht mehr. Ich habe auf TypeScript umgestellt und benannte Parameter verwendet.
Jeremy J Starcher
28

Mehrere Argumente beziehen sich meist auf obligatorische Parameter. Es ist nichts falsch mit ihnen.

Wenn Sie optionale Parameter haben, wird es kompliziert. Wenn sich einer von ihnen auf die anderen verlässt, so dass sie eine bestimmte Reihenfolge haben (z. B. benötigt der vierte die dritte), sollten Sie dennoch mehrere Argumente verwenden. Fast alle nativen EcmaScript- und DOM-Methoden funktionieren so. Ein gutes Beispiel ist die openMethode von XMLHTTPrequests , bei der die letzten drei Argumente optional sind. Die Regel lautet "kein Kennwort ohne Benutzer" (siehe auch MDN-Dokumente ).

Optionsobjekte sind in zwei Fällen nützlich:

  • Sie haben so viele Parameter, dass es verwirrend wird: Die "Benennung" hilft Ihnen, Sie müssen sich keine Gedanken über deren Reihenfolge machen (insbesondere, wenn sie sich ändern können).
  • Sie haben optionale Parameter. Die Objekte sind sehr flexibel und ohne Bestellung geben Sie einfach die Dinge weiter, die Sie benötigen, und nichts anderes undefined.

In Ihrem Fall würde ich empfehlen map(nodeList, callback, options). nodelistund callbackerforderlich sind, kommen die anderen drei Argumente nur gelegentlich und haben vernünftige Standardeinstellungen.

Ein anderes Beispiel ist JSON.stringify. Möglicherweise möchten Sie den spaceParameter verwenden, ohne eine replacerFunktion zu übergeben - dann müssen Sie aufrufen …, null, 4). Ein Argumentobjekt wäre möglicherweise besser gewesen, obwohl es für nur zwei Parameter nicht wirklich sinnvoll ist.

Bergi
quelle
+1 gleiche Frage wie @ trevor-dixon: Haben Sie gesehen, dass diese Mischung in der Praxis verwendet wird, zum Beispiel in js-Bibliotheken?
Christophe
Ein Beispiel könnten die Ajax-Methoden von jQuery sein . Sie akzeptieren die [obligatorische] URL als erstes Argument und ein großes Optionsargument als zweites.
Bergi
so seltsam! Ich habe das noch nie bemerkt. Ich habe immer gesehen, dass es mit der URL als Optionseigenschaft verwendet wird ...
Christophe
Ja, jQuery macht seltsame Dinge mit seinen optionalen Parametern, während es abwärtskompatibel bleibt :-)
Bergi
1
Meiner Meinung nach ist dies hier die einzig vernünftige Antwort.
Benjamin Gruenbaum
11

Die Verwendung des Ansatzes "Optionen als Objekt" ist am besten. Sie müssen sich keine Gedanken über die Reihenfolge der Eigenschaften machen und es gibt mehr Flexibilität bei der Datenübergabe (z. B. optionale Parameter).

Das Erstellen eines Objekts bedeutet auch, dass die Optionen problemlos für mehrere Funktionen verwendet werden können:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
quelle
Die (vernünftige) Annahme hier ist, dass das Objekt immer die gleichen Schlüsselpaare hat. Wenn Sie mit einer beschissenen / inkonsistenten API arbeiten, haben Sie ein anderes Problem.
Backdesk
9

Ich denke, wenn Sie etwas instanziieren oder eine Methode eines Objekts aufrufen, möchten Sie ein Optionsobjekt verwenden. Wenn es sich um eine Funktion handelt, die nur einen oder zwei Parameter verarbeitet und einen Wert zurückgibt, ist eine Argumentliste vorzuziehen.

In einigen Fällen ist es gut, beide zu verwenden. Wenn Ihre Funktion einen oder zwei erforderliche Parameter und eine Reihe optionaler Parameter hat, machen Sie die ersten beiden erforderlichen Parameter und den dritten zu einem optionalen Options-Hash.

In Ihrem Beispiel würde ich tun map(nodeList, callback, options). Knotenliste und Rückruf sind erforderlich. Es ist ziemlich einfach zu erkennen, was gerade passiert, indem Sie einen Anruf lesen, und es ist wie bei vorhandenen Kartenfunktionen. Alle anderen Optionen können als optionaler dritter Parameter übergeben werden.

Trevor Dixon
quelle
+1 interessant. Haben Sie es in der Praxis gesehen, zum Beispiel in js Bibliotheken?
Christophe
7

Ihr Kommentar zur Frage:

In meinem Beispiel sind die letzten drei optional.

Warum also nicht? (Hinweis: Dies ist ziemlich rohes Javascript. Normalerweise würde ich einen defaultHash verwenden und ihn mit den Optionen aktualisieren, die mit Object.extend oder JQuery.extend oder ähnlichem übergeben wurden.)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

Da es jetzt viel offensichtlicher ist, was optional ist und was nicht, sind all dies gültige Verwendungen der Funktion:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});
Izkata
quelle
Dies ähnelt der Antwort von @ trevor-dixon.
Christophe
5

Ich bin mit dieser Antwort vielleicht etwas spät dran, aber ich habe nach Meinungen anderer Entwickler zu diesem Thema gesucht und bin auf diesen Thread gestoßen.

Ich bin mit den meisten Antwortenden nicht einverstanden und stehe dem Ansatz der „Mehrfachargumente“ gegenüber. Mein Hauptargument ist, dass es andere Anti-Muster wie "Mutieren und Zurückgeben des Parameterobjekts" oder "Weitergeben des gleichen Parameterobjekts an andere Funktionen" entmutigt. Ich habe in Codebasen gearbeitet, die dieses Anti-Pattern ausgiebig missbraucht haben, und das Debuggen von Code, der dies tut, wird schnell unmöglich. Ich denke, dies ist eine sehr Javascript-spezifische Faustregel, da Javascript nicht stark typisiert ist und solche willkürlich strukturierten Objekte zulässt.

Meine persönliche Meinung ist, dass Entwickler beim Aufrufen von Funktionen explizit sein sollten, redundante Daten nicht weitergeben und Änderungen durch Verweise vermeiden sollten. Es ist nicht so, dass dieses Muster das Schreiben von präzisem, korrektem Code ausschließt. Ich habe nur das Gefühl, dass es für Ihr Projekt viel einfacher ist, in schlechte Entwicklungspraktiken zu verfallen.

Betrachten Sie den folgenden schrecklichen Code:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

Diese Art erfordert nicht nur eine explizitere Dokumentation, um die Absicht zu spezifizieren, sondern lässt auch Raum für vage Fehler. Was passiert , wenn ein Entwickler ändert param1inbar() ? Wie lange würde es Ihrer Meinung nach dauern, eine Codebasis von ausreichender Größe zu durchsuchen, um dies zu erfassen? Zugegeben, dieses Beispiel ist etwas unaufrichtig, da davon ausgegangen wird, dass Entwickler zu diesem Zeitpunkt bereits mehrere Anti-Patterns begangen haben. Es zeigt jedoch, wie das Übergeben von Objekten, die Parameter enthalten, mehr Raum für Fehler und Mehrdeutigkeiten bietet und ein höheres Maß an Gewissenhaftigkeit und die Einhaltung der Konstantenkorrektheit erfordert.

Nur meine zwei Cent zu diesem Thema!

ajxs
quelle
3

Es hängt davon ab, ob.

Basierend auf meiner Beobachtung zum Design dieser gängigen Bibliotheken sind hier die Szenarien, die wir als Optionsobjekt verwenden sollten:

  • Die Parameterliste ist lang (> 4).
  • Einige oder alle Parameter sind optional und hängen nicht von einer bestimmten Reihenfolge ab.
  • Die Parameterliste wird möglicherweise in zukünftigen API-Updates erweitert.
  • Die API wird von einem anderen Code aufgerufen, und der API-Name ist nicht klar genug, um die Bedeutung der Parameter zu bestimmen. Daher ist möglicherweise ein starker Parametername für die Lesbarkeit erforderlich.

Und Szenarien zur Verwendung der Parameterliste:

  • Die Parameterliste ist kurz (<= 4).
  • Die meisten oder alle Parameter sind erforderlich.
  • Optionale Parameter sind in einer bestimmten Reihenfolge. (dh: $ .get)
  • Die Bedeutung der Parameter lässt sich leicht anhand des API-Namens erkennen.
Lynn Ning
quelle
2

Objekt ist vorzuziehen, da es beim Übergeben eines Objekts einfach ist, die Anzahl der Eigenschaften in diesen Objekten zu erweitern, und Sie nicht auf die Reihenfolge achten müssen, in der Ihre Argumente übergeben wurden.

happyCoda
quelle
1

Für eine Funktion, die normalerweise einige vordefinierte Argumente verwendet, verwenden Sie besser das Optionsobjekt. Das entgegengesetzte Beispiel ist so etwas wie eine Funktion, die unendlich viele Argumente erhält wie: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).

Ilya Sidorovich
quelle
0

Ich würde mir große Javascript-Projekte ansehen.

Dinge wie Google Map werden Sie häufig sehen, dass instanziierte Objekte ein Objekt erfordern, aber Funktionen Parameter erfordern. Ich würde denken, dass dies mit OPTION-Argumenten zu tun hat.

Wenn Sie Standardargumente oder optionale Argumente benötigen, ist ein Objekt wahrscheinlich besser, weil es flexibler ist. Wenn Sie dies nicht tun, sind normale funktionale Argumente expliziter.

Javascript hat auch ein argumentsObjekt. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
quelle