wake-up-neo.com

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

Ich finde die genannte Parameter-Funktion 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 kann dafür jede Bibliothek verwenden.

152
Robin Maben

ES2015 und höher

In ES2015 kann parameter destructuring verwendet werden, um benannte Parameter zu simulieren. Der Aufrufer müsste ein Objekt übergeben, aber Sie können alle Überprüfungen in 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 einen Weg, dem, was Sie wollen, nahe zu kommen, aber er basiert auf der Ausgabe von Function.prototype.toString[ES5] , was zu einem gewissen Grad von der Implementierung abhängt, so dass es 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 mit dem entsprechenden Parameter verknüpfen können.

Ein Funktionsaufruf könnte dann so aussehen

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

dabei sind a und b 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);
        };
    };
}());

Als was würdest du verwenden:

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 (das ist etwas anderes als überhaupt keinen Wert zu haben). Das heißt, Sie können nicht mit arguments.length Testen, 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 verlängern Sie sogar Function.prototype, so dass Sie Folgendes tun können:

foo.execute(a, b, {posArg: ...});
149
Felix Kling

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

58
Utkanos

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

Viele Leute sagen, Sie sollten einfach den Trick "Ein 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-/Argumenthinweise 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)

Und das größte Problem bei der Verwendung des Tricks "Ein Objekt übergeben" ist, dass beim Aufrufen der Funktion der IDE) einen Tipp für einen einzelnen Typ gibt und das wars ... Wie sollen wir das machen? Wissen Sie, welche Parameter und Typen in das arg1-Objekt aufgenommen werden sollen?

I have no idea what parameters should go in arg1

Also ... der Trick "Ein Objekt übergeben" funktioniert bei mir nicht ... Es bereitet mir mehr Kopfzerbrechen, wenn ich mir den Dokumentblock 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 fürchterlich ist, neuen Code zu schreiben.

Nun, das ist die Technik, die ich benutze ... Nun, es kann einige Probleme damit geben, und einige Entwickler sagen mir, dass ich es falsch mache, und ich bin aufgeschlossen, wenn es um diese Dinge geht ... Ich bin immer bereit, nach besseren Wegen 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 mein IDE gibt mir alle richtigen Argumentationshinweise ... Und während ich später den Code pflege, habe ich Ich sehe auf einen Blick nicht nur den Wert, der an die Funktion übergeben wurde, sondern auch den Namen des Arguments. Der einzige Overhead, den ich sehe, besteht darin, Ihre Argumentnamen als lokale Variablen zu deklarieren, um den globalen Namespace nicht zu verschmutzen zusätzliche Eingabe, aber im Vergleich zu der Zeit, die zum Nachschlagen von Dokumentenblöcken beim Schreiben von neuem Code oder Verwalten von vorhandenem Code erforderlich ist, trivial.

Now, I have all the parameters and types when creating new code

25
Ray Perea

Wenn Sie klarstellen möchten, was die einzelnen Parameter sind, 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 es gewinnt an Lesbarkeit.

18
dav_i

Ein anderer Weg wäre die Verwendung von Attributen eines geeigneten Objekts, z. wie so:

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. Dies bedeutet, dass für jeden Parameter ein Member erstellt wird, das zu einer teilweise bewerteten Version der Funktion ausgewertet werden kann.

Diese Idee ist natürlich verwandt mit Schönfinkeling aka Currying.

6
Udo Klein

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 der Meinung bin, dass dies einen funktionalen Wert bietet, der über die von Ihnen bereits verwendete Technik hinausgeht, ist es semantisch etwas sauberer. Und es muss immer noch ein Objektverweis 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));
2
Allen Ellison

Versuchen von 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), Ich habe bemerkt, dass benannte Parameter fast implementiert sind, 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
1
jgran

Von Python das hat mich gestört. Ich habe einen einfachen Wrapper/Proxy für Node geschrieben, der sowohl Positions- als auch Keyword-Objekte ausnimmt.

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

0
Vince Spicer

Aufruf der Funktion f mit benannten Parametern, die als Objekt übergeben wurden

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

nennt im Grunde seine Zusammensetzung f(...g(o)), wobei ich die Spread-Syntax verwende und g eine "verbindliche" Abbildung ist, die die Objektwerte mit ihren Parameterpositionen verbindet.

Die Bindungskarte ist genau die fehlende Zutat, die durch die Anordnung 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 das drei entkoppelte 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, wobei die Bindung in der Funktionsdefinition beliebig angepasst und zum Zeitpunkt des Funktionsaufrufs beliebig erweitert werden kann.

Beispielsweise möchten Sie möglicherweise height und width als h und w in der Definition Ihrer Funktion abkürzen, um sie kürzer und übersichtlicher zu gestalten, solange Sie dies noch tun Ich möchte es der Klarheit halber mit vollständigen Namen bezeichnen:

// 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 Funktionsprogrammierung nützlicher, bei der Funktionen beliebig transformiert werden können, wobei ihre ursprünglichen Parameternamen verloren gehen.

0
Dmitri Zaitsev