Esiste un modo per fornire parametri denominati in una chiamata di funzione in JavaScript?


207

Trovo la funzionalità dei parametri nominati in C # abbastanza utile in alcuni casi.

calculateBMI(70, height: 175);

Cosa posso usare se lo voglio in JavaScript?


Quello che non voglio è questo:

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
}

Quell'approccio che ho già usato. C'è un altro modo?

Sto bene usando qualsiasi libreria per farlo.


Non credo sia possibile, ma puoi provare a mettere alcuni non definiti in posti vuoti. Che è molto male. Usa l'oggetto, va bene.
Vladislav Qulin,

14
No, JavaScript / EcmaScript non supportano i parametri con nome. Scusate.
smilly92,

1
Lo so già. Grazie. Stavo cercando un modo per modificare ciò che l'esistente Functionin javascript può fare.
Robin Maben,

1
L'esistente Functionin Javascript non può cambiare la sintassi principale di Javascript
Gareth,

2
Non credo che Javascript supporti questa funzione. Penso che il più vicino a cui si possa arrivare ai parametri nominati sia (1) aggiungere un commento calculateBMI(70, /*height:*/ 175);, (2) fornire un oggetto calculateBMI(70, {height: 175})o (3) usare una costante const height = 175; calculateBMI(70, height);.
tfmontague,

Risposte:


212

ES2015 e versioni successive

In ES2015, la destrutturazione dei parametri può essere utilizzata per simulare parametri denominati. Richiederebbe al chiamante di passare un oggetto, ma è possibile evitare tutti i controlli all'interno della funzione se si utilizzano anche i parametri predefiniti:

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

Esiste un modo per avvicinarsi a ciò che si desidera, ma si basa sull'output di Function.prototype.toString [ES5] , che dipende in una certa misura dall'implementazione, quindi potrebbe non essere compatibile con più browser.

L'idea è di analizzare i nomi dei parametri dalla rappresentazione di stringa della funzione in modo da poter associare le proprietà di un oggetto al parametro corrispondente.

Potrebbe quindi apparire una chiamata di funzione

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

dove ae bsono argomenti posizionali e l'ultimo argomento è un oggetto con argomenti denominati.

Per esempio:

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

Che useresti come:

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

Ci sono alcuni svantaggi di questo approccio (sei stato avvertito!):

  • Se l'ultimo argomento è un oggetto, viene trattato come un "oggetto argomento denominato"
  • Otterrai sempre quanti argomenti hai definito nella funzione, ma alcuni di essi potrebbero avere il valore undefined(che è diverso dal non avere alcun valore). Ciò significa che non è possibile utilizzare arguments.lengthper verificare quanti argomenti sono stati passati.

Invece di avere una funzione che crea il wrapper, potresti anche avere una funzione che accetta una funzione e vari valori come argomenti, come

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

o addirittura estenderti in Function.prototypemodo da poter fare:

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

Sì ... ecco un esempio per questo: jsfiddle.net/9U328/1 (anche se dovresti piuttosto usare Object.definePropertye impostare enumerablesu false). Bisogna sempre fare attenzione quando si estendono oggetti nativi. L'intero approccio sembra un po 'confuso, quindi non mi aspetto che funzioni ora e per sempre;)
Felix Kling

Notato. Scenderò a metterlo in pratica. Contrassegnare come risposta! ... Per ora;)
Robin Maben,

1
Nitpick molto minore: non credo che questo approccio catturerà le funzioni freccia di EcmaScript 6 . Al momento non è un grosso problema, ma potrebbe valere la pena menzionarlo nella sezione Avvertenze della tua risposta.
Nessuno,

3
@NobodyMan: True. Ho scritto questa risposta prima che le funzioni delle frecce fossero una cosa. In ES6, ricorrerei alla distruzione dei parametri.
Felix Kling,

1
Sulla questione di undefinedvs "nessun valore", si può aggiungere che questo è esattamente il modo in cui funzionano i valori predefiniti delle funzioni JS, trattandosi undefinedcome un valore mancante.
Dmitri Zaitsev,

71

No - l'approccio oggetto è la risposta di JavaScript a questo. Non vi è alcun problema con questo, a condizione che la funzione preveda un oggetto anziché parametri separati.


35
@RobertMaben - La risposta alla domanda specifica è che non esiste un modo nativo per raccogliere le variabili o le funzioni dichiarate senza sapere che vivono in un determinato spazio dei nomi. Solo perché la risposta è breve, non nega la sua idoneità come risposta - non sei d'accordo? Ci sono risposte molto più brevi là fuori, sulla falsariga di "no, no t possibile". Possono essere brevi, ma sono anche la risposta alla domanda.
Mitya,


1
Questa è sicuramente una risposta equa: "parametri nominati" è una caratteristica del linguaggio. L'approccio oggettivo è la cosa migliore successiva in assenza di tale caratteristica.
Fatuhoku,

32

Questo problema è stato un mio peeve pet per qualche tempo. Sono un programmatore esperto con molte lingue al mio attivo. Una delle mie lingue preferite che ho avuto il piacere di usare è Python. Python supporta i parametri nominati senza alcun trucco ... Da quando ho iniziato a usare Python (qualche tempo fa) tutto è diventato più semplice. Credo che ogni lingua dovrebbe supportare parametri nominati, ma non è così.

Molte persone dicono di usare semplicemente il trucco "Passa un oggetto" in modo da avere dei parametri nominati.

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

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

E funziona benissimo, tranne ..... quando si tratta della maggior parte degli IDE là fuori, molti di noi sviluppatori fanno affidamento su suggerimenti di tipo / argomento all'interno del nostro IDE. Uso personalmente PHP Storm (insieme ad altri IDE JetBrains come PyCharm per python e AppCode per Objective C)

E il problema più grande con l'utilizzo del trucco "Passa un oggetto" è che quando chiami la funzione, l'IDE ti dà un singolo suggerimento di tipo e basta ... Come dovremmo sapere quali parametri e tipi dovrebbero andare nel oggetto arg1?

Non ho idea di quali parametri dovrebbero andare in arg1

Quindi ... il trucco "Passa un oggetto" non funziona per me ... In realtà provoca più mal di testa nel dover guardare il blocco documenti di ogni funzione prima di sapere quali parametri si aspetta la funzione .... Certo, è ottimo per quando stai mantenendo il codice esistente, ma è orribile per scrivere nuovo codice.

Bene, questa è la tecnica che uso .... Ora, potrebbero esserci dei problemi con esso, e alcuni sviluppatori potrebbero dirmi che sto sbagliando, e ho una mente aperta quando si tratta di queste cose ... Sono sempre disposto a cercare modi migliori per eseguire un'attività ... Quindi, se c'è un problema con questa tecnica, allora i commenti sono benvenuti.

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

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

In questo modo, ho il meglio di entrambi i mondi ... il nuovo codice è facile da scrivere poiché il mio IDE mi dà tutti i suggerimenti sull'argomento corretto ... E, mantenendo il codice in seguito, posso vedere a colpo d'occhio, non solo il valore passato alla funzione, ma anche il nome dell'argomento. L'unico sovraccarico che vedo è dichiarare i nomi dei tuoi argomenti come variabili locali per evitare di inquinare lo spazio dei nomi globale. Certo, è un po 'di digitazione extra, ma banale rispetto al tempo impiegato per cercare i blocchi di documenti mentre si scrive nuovo codice o si mantiene il codice esistente.

Ora, ho tutti i parametri e i tipi durante la creazione di nuovo codice


2
L'unica cosa con questa tecnica è il fatto che non è possibile modificare l'ordine dei parametri ... Ma personalmente sto bene con quello.
Ray Perea,

14
Sembra che questo stia solo chiedendo problemi quando arriva qualche futuro manutentore e pensa di poter cambiare l'ordine degli argomenti (ma ovviamente non può).
nessuno

1
@AndrewMedico Sono d'accordo ... sembra che tu possa semplicemente cambiare l'ordine degli argomenti come in Python. L'unica cosa che posso dire a riguardo è che lo scopriranno molto velocemente quando cambiando l'ordine degli argomenti si rompe il programma.
Ray Perea,

7
Direi che myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');è meglio di myFunc(arg1='Param1', arg2='Param2');, perché non ha alcuna possibilità di ingannare il lettore di quanto stiano accadendo qualsiasi argomento reale chiamato
Eric

3
Lo schema proposto non ha nulla a che fare con gli argomenti nominati, l'ordine è ancora importante, i nomi non devono rimanere sincronizzati con i parametri effettivi, lo spazio dei nomi è inquinato da variabili non necessarie e sono implicite relazioni che non esistono.
Dmitri Zaitsev,

25

Se vuoi chiarire quali sono ciascuno dei parametri, piuttosto che semplicemente chiamare

someFunction(70, 115);

perché non fare quanto segue

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

certo, è una riga di codice aggiuntiva, ma vince sulla leggibilità.


5
+1 per seguire il principio KISS, inoltre aiuta con il debug. Tuttavia, penso che ogni var dovrebbe essere sulla sua linea, anche se con un leggero successo in termini di prestazioni ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb,

21
Non si tratta solo dell'ulteriore riga di codice, ma riguarda anche l'ordine degli argomenti e li rende facoltativi. Quindi potresti scriverlo con parametri nominati:, someFunction(height: 115);ma se scrivi someFunction(height);stai effettivamente impostando la larghezza.
Francisco Presencia,

In tal caso, CoffeeScript supporta argomenti denominati. Ti permetterà di scrivere solo someFunction(width = 70, height = 115);. Le variabili sono dichiarate nella parte superiore dell'ambito corrente nel codice JavaScript che viene generato.
Audun Olsen,

6

Un altro modo sarebbe quello di utilizzare gli attributi di un oggetto adatto, ad esempio in questo modo:

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

Naturalmente per questo esempio inventato è in qualche modo insignificante. Ma nei casi in cui appare la funzione

do_something(some_connection_handle, some_context_parameter, some_value)

potrebbe essere più utile. Potrebbe anche essere combinato con l'idea di "parametrizzazione" per creare un tale oggetto da una funzione esistente in modo generico. Cioè per ogni parametro creerebbe un membro che può valutare una versione valutata parzialmente della funzione.

Questa idea è ovviamente legata a Schönfinkeling aka Currying.


Questa è un'idea chiara e diventa ancora migliore se la si combina con i trucchi dell'argomento introspezione. Sfortunatamente, non riesce a lavorare con argomenti opzionali
Eric,

2

C'è un altro modo. Se si passa un oggetto per riferimento, le proprietà dell'oggetto verranno visualizzate nell'ambito locale della funzione. So che funziona per Safari (non ho controllato altri browser) e non so se questa funzione abbia un nome, ma l'esempio seguente ne illustra l'utilizzo.

Anche se in pratica non penso che questo offra alcun valore funzionale oltre la tecnica che stai già utilizzando, è un po 'più pulito semanticamente. E richiede ancora il passaggio di un riferimento a un oggetto o di un oggetto letterale.

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

Provando Node-6.4.0 (process.versions.v8 = '5.0.71.60') e Node Chakracore-v7.0.0-pre8 e poi Chrome-52 (V8 = 5.2.361.49), ho notato che i parametri nominati sono quasi implementato, ma quell'ordine ha ancora la precedenza. Non riesco a trovare ciò che dice lo standard ECMA.

>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

Ma l'ordine è richiesto !! È il comportamento standard?

> f(b=10)
a=10 + b=2 = 12

10
Non sta facendo quello che pensi. Il risultato b=10dell'espressione è 10ed è quello che viene passato alla funzione. f(bla=10)funzionerebbe anche (assegna 10 alla variabile blae in seguito passa il valore alla funzione).
Matthias Kestenholz,

1
Questo sta anche creando ae bvariabili nell'ambito globale come effetto collaterale.
Gershom l'

2

Funzione fdi chiamata con parametri denominati passati come oggetto

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

sta fondamentalmente chiamando la sua composizione f(...g(o))dove sto usando la sintassi di diffusione ed gè una mappa "vincolante" che collega i valori degli oggetti con le loro posizioni dei parametri.

La mappa vincolante è precisamente l'ingrediente mancante, che può essere rappresentato dalla matrice delle sue chiavi:

// 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}))

Nota i tre ingredienti disaccoppiati: la funzione, l'oggetto con argomenti denominati e l'associazione. Questo disaccoppiamento consente molta flessibilità nell'uso di questo costrutto, in cui l'associazione può essere arbitrariamente personalizzata nella definizione della funzione e arbitrariamente estesa al momento della chiamata della funzione.

Ad esempio, potresti voler abbreviare heighte widthcome he wall'interno della definizione della tua funzione, per renderlo più breve e più pulito, mentre vuoi ancora chiamarlo con nomi completi per chiarezza:

// 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})

Questa flessibilità è anche più utile per la programmazione funzionale, in cui le funzioni possono essere arbitrariamente trasformate perdendo i nomi dei parametri originali.


0

Venendo da Python questo mi ha infastidito. Ho scritto un semplice wrapper / proxy per nodo che accetterà sia oggetti posizionali che parole chiave.

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


Se ho capito bene, la tua soluzione richiede di distinguere i parametri posizionali e nominati nella definizione della funzione, il che significa che non ho la libertà di fare questa scelta al momento della chiamata.
Dmitri Zaitsev il

@DmitriZaitsev: nella comunità di Python (con argomenti di parole chiave come una caratteristica di tutti i giorni), riteniamo un'ottima idea poter forzare gli utenti a specificare argomenti opzionali per parola chiave; questo aiuta ad evitare errori.
Tobias,

@Tobias Forzare gli utenti a farlo in JS è un problema risolto:, f(x, opt)dov'è optun oggetto. Resta da chiedersi se sia utile evitare o creare errori (come causati dall'ortografia errata e dal dolore nel ricordare i nomi delle parole chiave).
Dmitri Zaitsev il

@DmitriZaitsev: in Python questo evita rigorosamente gli errori, perché (ovviamente) gli argomenti delle parole chiave sono una caratteristica del linguaggio centrale. Per argomenti di sole parole chiave , Python 3 ha una sintassi speciale (mentre in Python 2 dovresti estrarre le chiavi dal kwargs dict una alla volta e infine alzare un TypeErrorse rimangono delle chiavi sconosciute). La tua f(x, opt)soluzione consente alla ffunzione di fare qualcosa di simile in Python 2, ma dovrai gestire tu stesso tutti i valori predefiniti.
Tobias,

@Tobias Questa proposta è pertinente per JS? Sembra descrivere come f(x, opt)sta già funzionando, mentre non vedo come questo aiuti a rispondere alla domanda, dove ad esempio vuoi fare entrambe le cose request(url)e request({url: url})ciò non sarebbe possibile facendo urlun parametro solo per parole chiave.
Dmitri Zaitsev,

-1

Contrariamente a quanto si crede comunemente, i parametri con nome possono essere implementati in JavaScript standard di vecchia scuola (solo per parametri booleani) mediante una convenzione di codifica semplice e accurata, come mostrato di seguito.

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

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

Avvertenze:

  • L'ordinamento degli argomenti deve essere preservato, ma il modello è ancora utile, poiché rende ovvio quale argomento reale è inteso per quale parametro formale senza dover grep per la firma della funzione o utilizzare un IDE.

  • Questo funziona solo per i booleani. Tuttavia, sono sicuro che un modello simile potrebbe essere sviluppato per altri tipi utilizzando la semantica di coercizione del tipo univoco di JavaScript.


Ti stai ancora riferendo per posizione, non per nome qui.
Dmitri Zaitsev il

@DmitriZaitsev sì, l'ho anche detto sopra. Tuttavia, lo scopo degli argomenti denominati è di chiarire al lettore cosa significa ogni argomento; è una forma di documentazione. La mia soluzione consente di incorporare la documentazione nella chiamata di funzione senza ricorrere a commenti, che sembrano disordinati.
user234461,

Questo risolve un problema diverso. La domanda riguardava il passaggio heightper nome indipendentemente dall'ordine dei parametri.
Dmitri Zaitsev,

@DmitriZaitsev in realtà, la domanda non diceva nulla sull'ordine dei parametri o sul motivo per cui OP voleva usare i parametri nominati.
user234461,

Fa riferimento a C # dove passare i parametri per nome in qualsiasi ordine è la chiave.
Dmitri Zaitsev,
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.