Gibt es eine Möglichkeit, benannte Parameter in einem Funktionsaufruf in JavaScript bereitzustellen?

207

Ich finde die Funktion für benannte Parameter in C # in einigen Fällen sehr nützlich.

calculateBMI(70, height: 175);

Was kann ich verwenden, wenn ich dies in JavaScript möchte?


Was ich nicht will ist folgendes:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

Diesen Ansatz habe ich bereits verwendet. Gibt es eine andere Art und Weise?

Ich bin in Ordnung, wenn ich dazu eine Bibliothek benutze.

Robin Maben
quelle
Ich denke nicht, dass dies möglich ist, aber Sie können versuchen, einige undefinierte an leere Stellen zu setzen. Welches ist so schlecht. Verwenden Sie das Objekt, es ist gut.
Vladislav Qulin
14
Nein, JavaScript / EcmaScript unterstützt keine benannten Parameter. Es tut uns leid.
smilly92
1
Das weiß ich schon. Vielen Dank. Ich suchte nach einer Möglichkeit, um zu optimieren, was das Functionin Javascript vorhandene kann.
Robin Maben
1
Das Functionin Javascript vorhandene kann die
Gareth
2
Ich glaube nicht, dass Javascript diese Funktion unterstützt. Ich denke, Sie können benannten Parametern am nächsten kommen, indem Sie (1) einen Kommentar hinzufügen calculateBMI(70, /*height:*/ 175);, (2) ein Objekt bereitstellen calculateBMI(70, {height: 175})oder (3) eine Konstante verwenden const height = 175; calculateBMI(70, height);.
tfmontague

Antworten:

210

ES2015 und höher

In ES2015 kann die Parameterdestrukturierung verwendet werden, um benannte Parameter zu simulieren. Der Aufrufer müsste ein Objekt übergeben, aber Sie können alle Überprüfungen innerhalb der Funktion vermeiden, wenn Sie auch Standardparameter verwenden:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

Es gibt eine Möglichkeit, dem, was Sie wollen, nahe zu kommen, aber sie basiert auf der Ausgabe von Function.prototype.toString [ES5] , die in gewissem Maße von der Implementierung abhängt, sodass sie möglicherweise nicht browserübergreifend kompatibel ist.

Die Idee ist, die Parameternamen aus der Zeichenfolgendarstellung der Funktion zu analysieren, damit Sie die Eigenschaften eines Objekts dem entsprechenden Parameter zuordnen können.

Ein Funktionsaufruf könnte dann so aussehen

func(a, b, {someArg: ..., someOtherArg: ...});

wo aund bsind Positionsargumente und das letzte Argument ist ein Objekt mit benannten Argumenten.

Beispielsweise:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

Welche würden Sie verwenden als:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

DEMO

Dieser Ansatz weist einige Nachteile auf (Sie wurden gewarnt!):

  • Wenn das letzte Argument ein Objekt ist, wird es als "benannte Argumentobjekte" behandelt.
  • Sie erhalten immer so viele Argumente, wie Sie in der Funktion definiert haben, aber einige von ihnen haben möglicherweise den Wert undefined(der sich davon unterscheidet, überhaupt keinen Wert zu haben). Das heißt, Sie können nicht arguments.lengthtesten, wie viele Argumente übergeben wurden.

Anstatt dass eine Funktion den Wrapper erstellt, können Sie auch eine Funktion verwenden, die eine Funktion und verschiedene Werte als Argumente akzeptiert, z

call(func, a, b, {posArg: ... });

oder sogar verlängern, Function.prototypedamit Sie tun können:

foo.execute(a, b, {posArg: ...});
Felix Kling
quelle
Ja ... hier ist ein Beispiel dafür: jsfiddle.net/9U328/1 (obwohl Sie sollten eher verwenden Object.definePropertyund Satz enumerablezu false). Man sollte immer vorsichtig sein, wenn man native Objekte erweitert. Der ganze Ansatz fühlt sich ein bisschen hackig an, daher würde ich nicht erwarten, dass er jetzt und für immer funktioniert;)
Felix Kling
Notiert. Ich werde mich darauf konzentrieren, dies zu nutzen. Als Antwort markieren! ... für jetzt;)
Robin Maben
1
Sehr kleinerer nitpick: Ich glaube nicht , dieser Ansatz EcmaScript 6 fangen Pfeil Funktionen . Derzeit kein großes Problem, aber möglicherweise im Abschnitt mit den Vorbehalten Ihrer Antwort erwähnenswert.
NobodyMan
3
@NobodyMan: Richtig. Ich habe diese Antwort geschrieben, bevor Pfeilfunktionen eine Sache waren. In ES6 würde ich tatsächlich auf die Zerstörung von Parametern zurückgreifen.
Felix Kling
1
In Bezug auf undefinedvs "no value" kann hinzugefügt werden, dass genau so die Standardwerte von JS-Funktionen funktionieren - undefinedals fehlender Wert behandelt.
Dmitri Zaitsev
71

Nein - der Objektansatz ist die Antwort von JavaScript darauf. Dies ist kein Problem, vorausgesetzt, Ihre Funktion erwartet ein Objekt und keine separaten Parameter.

Mitya
quelle
34
@RobertMaben - Die Antwort auf die gestellte Frage lautet, dass es keine native Möglichkeit gibt, deklarierte Variablen oder Funktionen zu sammeln, ohne zu wissen, dass sie in einem bestimmten Namespace leben. Nur weil die Antwort kurz ist, negiert sie nicht ihre Eignung als Antwort - würden Sie nicht zustimmen? Es gibt viel kürzere Antworten, ähnlich wie "Nein, nein, nicht möglich". Sie mögen kurz sein, aber sie sind auch die Antwort auf die Frage.
Mitya
3
Heutzutage gibt es jedoch es6: 2ality.com/2011/11/keyword-parameters.html
Slacktracer
1
Dies ist definitiv eine faire Antwort - 'Named Parameters' ist eine Sprachfunktion. Der Objektansatz ist das nächstbeste, wenn ein solches Merkmal fehlt.
Fatuhoku
32

Dieses Problem ist seit einiger Zeit ein Liebling von mir. Ich bin ein erfahrener Programmierer mit vielen Sprachen. Eine meiner Lieblingssprachen, die ich gerne benutzt habe, ist Python. Python unterstützt benannte Parameter ohne Tricks .... Seit ich vor einiger Zeit mit Python angefangen habe, wurde alles einfacher. Ich glaube, dass jede Sprache benannte Parameter unterstützen sollte, aber das ist einfach nicht der Fall.

Viele Leute sagen, sie sollen nur den Trick "Objekt übergeben" verwenden, damit Sie Parameter benannt haben.

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

Und das funktioniert großartig, außer ... wenn es um die meisten IDEs geht, verlassen sich viele von uns Entwicklern auf Typ- / Argument-Hinweise in unserer IDE. Ich persönlich benutze PHP Storm (zusammen mit anderen JetBrains-IDEs wie PyCharm für Python und AppCode für Objective C)

Das größte Problem bei der Verwendung des Tricks "Objekt übergeben" besteht darin, dass die IDE beim Aufrufen der Funktion einen einzigen Typhinweis gibt und das war's ... Wie sollen wir wissen, welche Parameter und Typen in die Funktion aufgenommen werden sollen? arg1 Objekt?

Ich habe keine Ahnung, welche Parameter in arg1 gehen sollen

Also ... der Trick "Objekt übergeben" funktioniert bei mir nicht ... Es verursacht tatsächlich mehr Kopfschmerzen, wenn ich mir den Docblock jeder Funktion ansehen muss, bevor ich weiß, welche Parameter die Funktion erwartet ... Sicher, es ist großartig für Wenn Sie vorhandenen Code beibehalten, es aber schrecklich ist, neuen Code zu schreiben.

Nun, dies ist die Technik, die ich verwende ... Nun, es kann einige Probleme geben, und einige Entwickler sagen mir vielleicht, dass ich es falsch mache, und ich bin offen für diese Dinge ... Ich bin immer bereit, nach besseren Möglichkeiten zu suchen, um eine Aufgabe zu erfüllen ... Wenn es also ein Problem mit dieser Technik gibt, sind Kommentare willkommen.

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

Auf diese Weise habe ich das Beste aus beiden Welten ... neuer Code ist einfach zu schreiben, da meine IDE mir alle richtigen Argumentationshinweise gibt ... Und während ich später den Code pflege, kann ich auf einen Blick nicht nur den sehen Wert, der an die Funktion übergeben wird, aber auch der Name des Arguments. Der einzige Aufwand, den ich sehe, besteht darin, Ihre Argumentnamen als lokale Variablen zu deklarieren, um zu verhindern, dass der globale Namespace verschmutzt wird. Sicher, es ist ein bisschen mehr Tipparbeit, aber trivial im Vergleich zu der Zeit, die benötigt wird, um Docblocks nachzuschlagen, während neuer Code geschrieben oder vorhandener Code beibehalten wird.

Jetzt habe ich alle Parameter und Typen beim Erstellen von neuem Code

Ray Perea
quelle
2
Das einzige, was mit dieser Technik zu tun hat, ist die Tatsache, dass Sie die Reihenfolge der Parameter nicht ändern können ... Ich persönlich bin damit einverstanden.
Ray Perea
13
Scheint, als würde dies nur um Ärger bitten, wenn ein zukünftiger Betreuer vorbeikommt und denkt, dass er die Argumentreihenfolge ändern kann (aber offensichtlich nicht kann).
niemand
1
@ AndrewMedico Ich stimme zu ... es sieht so aus, als könnten Sie einfach die Argumentreihenfolge wie in Python ändern. Das einzige, was ich dazu sagen kann, ist, dass sie es sehr schnell herausfinden, wenn das Ändern der Argumentreihenfolge das Programm bricht.
Ray Perea
7
Ich würde behaupten, das myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');ist besser als myFunc(arg1='Param1', arg2='Param2');, weil das keine Chance hat, den Leser zu täuschen, als irgendwelche tatsächlich genannten Argumente passieren
Eric
2
Das vorgeschlagene Muster hat nichts mit benannten Argumenten zu tun, die Reihenfolge spielt immer noch eine Rolle, die Namen müssen nicht mit den tatsächlichen Parametern synchron bleiben, der Namespace ist mit unnötigen Variablen verschmutzt und es werden Beziehungen impliziert, die nicht vorhanden sind.
Dmitri Zaitsev
24

Wenn Sie klarstellen möchten, um welche Parameter es sich handelt, anstatt nur aufzurufen

someFunction(70, 115);

Warum nicht folgendes tun?

var width = 70, height = 115;
someFunction(width, height);

Sicher, es ist eine zusätzliche Codezeile, aber sie gewinnt an Lesbarkeit.

dav_i
quelle
4
+1 für das Befolgen des KISS-Prinzips und hilft auch beim Debuggen. Ich denke jedoch, dass jede Variable in einer eigenen Zeile stehen sollte, wenn auch mit einem leichten Leistungseinbruch ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
Clairestreb
21
Es geht nicht nur um die zusätzliche Codezeile, sondern auch um die Reihenfolge der Argumente und darum, sie optional zu machen. Sie könnten dies also mit benannten Parametern schreiben : someFunction(height: 115);, aber wenn Sie schreiben, stellen someFunction(height);Sie tatsächlich die Breite ein.
Francisco Presencia
In diesem Fall unterstützt CoffeeScript benannte Argumente. Damit können Sie einfach schreiben someFunction(width = 70, height = 115);. Die Variablen werden im generierten JavaScript-Code am oberen Rand des aktuellen Bereichs deklariert.
Audun Olsen
6

Eine andere Möglichkeit wäre, Attribute eines geeigneten Objekts zu verwenden, z. B. wie folgt:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

Natürlich ist es für dieses erfundene Beispiel etwas bedeutungslos. Aber in Fällen, in denen die Funktion aussieht

do_something(some_connection_handle, some_context_parameter, some_value)

es könnte nützlicher sein. Es könnte auch mit der Idee "parameterfy" kombiniert werden, ein solches Objekt auf generische Weise aus einer vorhandenen Funktion zu erstellen. Das heißt, für jeden Parameter wird ein Element erstellt, das zu einer teilweise ausgewerteten Version der Funktion ausgewertet werden kann.

Diese Idee hängt natürlich mit Schönfinkeling aka Currying zusammen.

Udo Klein
quelle
Dies ist eine nette Idee und wird noch besser, wenn Sie sie mit den Tricks der Selbstbeobachtung kombinieren. Leider funktioniert es nicht mit optionalen Argumenten
Eric
2

Es geht auch anders. Wenn Sie ein Objekt als Referenz übergeben, werden die Eigenschaften dieses Objekts im lokalen Bereich der Funktion angezeigt. Ich weiß, dass dies für Safari funktioniert (habe andere Browser nicht überprüft) und ich weiß nicht, ob diese Funktion einen Namen hat, aber das folgende Beispiel veranschaulicht ihre Verwendung.

Obwohl ich in der Praxis nicht denke, dass dies einen funktionalen Wert bietet, der über die bereits verwendete Technik hinausgeht, ist es semantisch etwas sauberer. Außerdem muss eine Objektreferenz oder ein Objektliteral übergeben werden.

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));
Allen Ellison
quelle
2

Beim Versuch mit Node-6.4.0 (process.versions.v8 = '5.0.71.60') und Node Chakracore-v7.0.0-pre8 und dann Chrome-52 (V8 = 5.2.361.49) habe ich festgestellt, dass benannte Parameter fast sind implementiert, aber diese Reihenfolge hat immer noch Vorrang. Ich kann nicht finden, was der ECMA-Standard sagt.

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

Aber Bestellung ist erforderlich !! Ist es das Standardverhalten?

> f(b=10)
a=10 + b=2 = 12
jgran
quelle
10
Das macht nicht was du denkst. Das Ergebnis des b=10Ausdrucks ist 10, und das wird an die Funktion übergeben. f(bla=10)würde auch funktionieren (es weist der Variablen 10 zu blaund übergibt den Wert anschließend an die Funktion).
Matthias Kestenholz
1
Dies ist auch die Schaffung aund bVariablen im globalen Bereich als Nebeneffekt.
Gershom
2

Aufruffunktion fmit benannten Parametern als Objekt übergeben

o = {height: 1, width: 5, ...}

nennt im Grunde seine Zusammensetzung, f(...g(o))wo ich die Spread-Syntax verwende und gist eine "bindende" Map, die die Objektwerte mit ihren Parameterpositionen verbindet.

Die Bindungskarte ist genau die fehlende Zutat, die durch das Array ihrer Schlüssel dargestellt werden kann:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

Beachten Sie die drei entkoppelten Bestandteile: die Funktion, das Objekt mit den benannten Argumenten und die Bindung. Diese Entkopplung ermöglicht eine große Flexibilität bei der Verwendung dieses Konstrukts, bei dem die Bindung in der Funktionsdefinition beliebig angepasst und zum Zeitpunkt des Funktionsaufrufs beliebig erweitert werden kann.

Zum Beispiel möchten Sie möglicherweise abkürzen heightund widthals hund winnerhalb der Definition Ihrer Funktion, um sie kürzer und sauberer zu machen, während Sie sie aus Gründen der Klarheit immer noch mit vollständigen Namen aufrufen möchten:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

Diese Flexibilität ist auch für die funktionale Programmierung nützlicher, bei der Funktionen beliebig transformiert werden können, wobei ihre ursprünglichen Parameternamen verloren gehen.

Dmitri Zaitsev
quelle
0

Als ich aus Python kam, nervte mich das. Ich habe einen einfachen Wrapper / Proxy für Knoten geschrieben, der sowohl Positions- als auch Schlüsselwortobjekte akzeptiert.

https://github.com/vinces1979/node-def/blob/master/README.md

Vince Spicer
quelle
Wenn ich das richtig verstehe, muss Ihre Lösung in der Funktionsdefinition zwischen Positions- und benannten Parametern unterscheiden, was bedeutet, dass ich nicht die Freiheit habe, diese Auswahl zum Zeitpunkt des Aufrufs zu treffen.
Dmitri Zaitsev
@DmitriZaitsev: In der Python - Community (mit Keyword - Argumente sind ein alltägliches Feature), halten wir es für eine sehr gute Idee zu der Lage sein , Benutzer zu zwingen , optionale Argumente nach Stichwort angeben; Dies hilft, Fehler zu vermeiden.
Tobias
@Tobias Das Erzwingen von Benutzern in JS ist ein gelöstes Problem: f(x, opt)Wo optbefindet sich ein Objekt ? Ob es hilft, Fehler zu vermeiden oder zu verursachen (z. B. durch Rechtschreibfehler und den Schmerz, sich Schlüsselwortnamen zu merken), bleibt eine Frage.
Dmitri Zaitsev
@DmitriZaitsev: In Python werden Fehler strikt vermieden , da (natürlich) Schlüsselwortargumente dort ein zentrales Sprachmerkmal sind. Für keyword- nur Argumente, Python 3 hat eine spezielle Syntax (während in Python 2 Sie die Schlüssel aus dem Pop würde kwargs dict eins nach dem anderen und schließlich eine erhöhen , TypeErrorwenn unbekannte Schlüssel gelassen werden). Ihre f(x, opt)Lösung ermöglicht es der fFunktion, etwas wie in Python 2 zu tun, aber Sie müssten alle Standardwerte selbst behandeln.
Tobias
@Tobias Ist dieser Vorschlag für JS relevant? Es scheint zu beschreiben, wie f(x, opt)es bereits funktioniert, obwohl ich nicht sehe, wie dies zur Beantwortung der Frage beiträgt, wo z. B. Sie beides tun möchten request(url)und request({url: url})dies nicht möglich wäre , wenn Sie urlnur einen Schlüsselwortparameter erstellen .
Dmitri Zaitsev
-1

Im Gegensatz zu dem, was allgemein angenommen wird, können benannte Parameter in Standard-JavaScript der alten Schule (nur für boolesche Parameter) mithilfe einer einfachen, übersichtlichen Codierungskonvention implementiert werden, wie unten gezeigt.

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

Vorsichtsmaßnahmen:

  • Die Reihenfolge der Argumente muss beibehalten werden - das Muster ist jedoch weiterhin nützlich, da deutlich wird, welches tatsächliche Argument für welchen formalen Parameter bestimmt ist, ohne nach der Funktionssignatur suchen oder eine IDE verwenden zu müssen.

  • Dies funktioniert nur für Boolesche Werte. Ich bin jedoch sicher, dass ein ähnliches Muster für andere Typen unter Verwendung der einzigartigen Typ-Zwangssemantik von JavaScript entwickelt werden könnte.

user234461
quelle
Sie beziehen sich immer noch auf die Position, hier nicht auf den Namen.
Dmitri Zaitsev
@DmitriZaitsev ja, das habe ich sogar oben gesagt. Der Zweck der genannten Argumente besteht jedoch darin, dem Leser klar zu machen, was jedes Argument bedeutet. Es ist eine Form der Dokumentation. Mit meiner Lösung kann die Dokumentation in den Funktionsaufruf eingebettet werden, ohne auf Kommentare zurückgreifen zu müssen, die unordentlich aussehen.
user234461
Dies löst ein anderes Problem. Bei der Frage ging es darum, heightunabhängig von der Reihenfolge der Parameter einen Namen zu vergeben.
Dmitri Zaitsev
@DmitriZaitsev Eigentlich sagte die Frage nichts über die Parameterreihenfolge aus oder warum OP benannte Parameter verwenden wollte.
user234461
Dies geschieht unter Bezugnahme auf C #, wobei die Übergabe von Parametern nach Namen in beliebiger Reihenfolge der Schlüssel ist.
Dmitri Zaitsev