Există o modalitate de a furniza parametri numiți într-un apel de funcție în JavaScript? (Programare, Javascript, Funcție, Parametri Opționali, Parametrii Numiți)

Robin Maben a intrebat.

Consider că funcția de parametri numiți din C# este foarte utilă în unele cazuri.

calculateBMI(70, height: 175);

Ce pot folosi dacă doresc acest lucru în JavaScript?


Ceea ce nu vreau este următorul lucru:

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
}

Această abordare am folosit-o deja. Există o altă cale?

Nu mă deranjează să folosesc orice bibliotecă pentru a face acest lucru.

Comentarii

  • Nu cred că acest lucru este posibil, dar puteți încerca să puneți niște undefined’s în locuri goale. Ceea ce este foarte rău. Folosiți obiectul, este bun. –  > Por Vladislav Qulin.
  • 18

  • Nu, JavaScript/EcmaScript nu acceptă parametrii numiți. Îmi pare rău. –  > Por smilly92.
  • Știu deja acest lucru. Mulțumesc. Căutam o modalitate care să implice o modificare a ceea ce este existent Function în javascript poate face. –  > Por Robin Maben.
  • Existent Function în Javascript nu poate schimba sintaxa de bază a Javascript –  > Por Gareth.
  • Nu cred că Javascript suportă această caracteristică. Cred că cel mai aproape de parametrii numiți este (1) adăugați un comentariu calculateBMI(70, /*height:*/ 175);, , (2) să oferiți un obiect calculateBMI(70, {height: 175}), , sau (3) să folosiți o constantă const height = 175; calculateBMI(70, height);. –  > Por tim-montague.
10 răspunsuri
Felix Kling

ES2015 și ulterior

În ES2015, destructurarea parametrilor poate fi utilizată pentru a simula parametrii numiți. Aceasta ar necesita ca apelantul să treacă un obiect, dar puteți evita toate verificările din interiorul funcției dacă utilizați și parametri impliciți:

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

Există o modalitate de a vă apropia de ceea ce doriți, dar aceasta se bazează pe ieșirea lui Function.prototype.toString [ES5], , care depinde într-o anumită măsură de implementare, astfel încât este posibil să nu fie compatibilă cu mai multe browsere.

Ideea este de a analiza numele parametrilor din reprezentarea șirului de caractere a funcției, astfel încât să puteți asocia proprietățile unui obiect cu parametrul corespunzător.

Un apel de funcție ar putea arăta astfel

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

unde a și b sunt argumente poziționale, iar ultimul argument este un obiect cu argumente numite.

De exemplu:

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

Pe care l-ați putea folosi ca:

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

Există câteva dezavantaje la această abordare (ați fost avertizați!):

  • Dacă ultimul argument este un obiect, acesta este tratat ca un „argument numit obiecte”
  • Veți obține întotdeauna atâtea argumente câte ați definit în funcție, dar unele dintre ele ar putea avea valoarea undefined (ceea ce este diferit de a nu avea nicio valoare). Acest lucru înseamnă că nu puteți utiliza arguments.length pentru a testa câte argumente au fost transmise.

În loc să aveți o funcție care să creeze învelișul, ați putea avea și o funcție care să accepte o funcție și diverse valori ca argumente, cum ar fi

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

sau chiar să extindă Function.prototype astfel încât să puteți face:

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

Comentarii

  • Da… Iată un exemplu în acest sens: jsfiddle.net/9U328/1 ( deși ar trebui mai degrabă să folosiți Object.defineProperty și să setați enumerable la false). Întotdeauna trebuie să fim atenți atunci când extindem obiecte native. Întreaga abordare pare un pic hacky, așa că nu m-aș aștepta ca ea să funcționeze acum și pentru totdeauna 😉 –  > Por Felix Kling.
  • Foarte minor nitpick:Nu cred că această abordare va prinde EcmaScript 6 Funcții de săgeată. Nu este o preocupare uriașă în prezent, dar ar putea merita să fie menționată în secțiunea de avertismente a răspunsului dvs. –  > Por NobodyMan.
  • @NobodyMan: Adevărat. Am scris acest răspuns înainte ca funcțiile săgeată să fie un lucru. În ES6, aș recurge, de fapt, la destructurarea parametrilor. –  > Por Felix Kling.
  • În ceea ce privește problema undefined vs. „fără valoare”, se poate adăuga că exact așa funcționează valorile implicite ale funcțiilor JS – tratarea undefined ca o valoare lipsă. –  > Por Dmitri Zaitsev.
  • @Yarin: Această „soluție” presupune că, dacă un obiect este trecut ca ultim argument la apelul funcției, atunci obiectul conține valori pentru parametrii „numiți”. Dar această presupunere nu funcționează dacă funcția se așteaptă în mod natural să i se transmită un obiect ca argument. De exemplu, luați în considerare function deleteUser(user) { ... }. deleteUser({name: ...}) nu funcționează, deoarece obiectul este interpretat ca „obiect parametru numit obiect”. Ar trebui să scrieți deleteUser({user: {name: ...}}). –  > Por Felix Kling.
Mitya

Nu – abordarea obiectelor este răspunsul lui JavaScript la această problemă. Nu există nicio problemă în acest sens, cu condiția ca funcția dvs. să se aștepte la un obiect, mai degrabă decât la parametri separați.

Comentarii

    38

  • @RobertMaben – Răspunsul la întrebarea specifică adresată este că nu există o modalitate nativă de a colecta vars sau funcții declarate fără a ști că acestea locuiesc pe un anumit spațiu de nume. Doar pentru că răspunsul este scurt, acest lucru nu îi anulează caracterul adecvat ca răspuns – nu sunteți de acord? Există răspunsuri mult mai scurte, de genul „nu, nu t posibil”. Poate că sunt scurte, dar sunt, de asemenea, răspunsul la întrebare. –  > Por Mitya.
  • În zilele noastre există totuși es6: 2ality.com/2011/11/11/keyword-parameters.html –  > Por slacktracer.
  • Acesta este cu siguranță un răspuns corect – „parametrii numiți” este o caracteristică a limbajului. Abordarea obiectelor este cel mai bun lucru următor în absența unei astfel de caracteristici. –  > Por fatuhoku.
Ray Perea

O mulțime de oameni spun să folosiți doar trucul „Pass an object” pentru a avea parametri numiți.

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

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

Și asta funcționează foarte bine, cu excepția….. când vine vorba de majoritatea IDE-urilor, mulți dintre noi, dezvoltatorii, ne bazăm pe indicii de tip / argument în cadrul IDE-ului nostru. Eu personal folosesc PHP Storm (împreună cu alte IDE-uri JetBrains, cum ar fi PyCharm pentru python și AppCode pentru Objective C).

Și cea mai mare problemă cu utilizarea trucului „Pass an object” este că, atunci când apelați funcția, IDE-ul vă oferă un singur indiciu de tip și atât… Cum ar trebui să știm ce parametri și ce tipuri ar trebui să intre în obiectul arg1?

Deci… trucul „Pass an object” nu funcționează pentru mine… De fapt, cauzează mai multe dureri de cap, deoarece trebuie să mă uit la fiecare docblock al funcției înainte de a ști ce parametri așteaptă funcția…. Sigur, este grozav pentru atunci când întreții codul existent, dar este oribil pentru a scrie cod nou.

Ei bine, aceasta este tehnica pe care o folosesc eu…. Acum, s-ar putea să existe unele probleme cu ea, iar unii dezvoltatori ar putea să-mi spună că o fac greșit, iar eu am o minte deschisă când vine vorba de aceste lucruri… Sunt întotdeauna dispus să mă uit la modalități mai bune de a îndeplini o sarcină… Deci, dacă există o problemă cu această tehnică, atunci comentariile sunt binevenite.

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

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

În acest fel, am ce e mai bun din ambele lumi… noul cod este ușor de scris, deoarece IDE-ul meu îmi oferă toate indicațiile adecvate pentru argumente… Și, în timp ce întrețin codul mai târziu, pot vedea dintr-o privire, nu numai valoarea transmisă funcției, ci și numele argumentului. Singurul cost suplimentar pe care îl văd este declararea numelor de argumente ca variabile locale pentru a nu polua spațiul de nume global. Sigur, este un pic de tastare suplimentară, dar este trivial în comparație cu timpul necesar pentru a căuta blocuri de documente în timp ce se scrie cod nou sau se întreține codul existent.

Comentarii

  • Singura problemă cu această tehnică este faptul că nu puteți schimba ordinea parametrilor… Totuși, personal, nu mă deranjează acest lucru. –  > Por Ray Perea.
  • 18

  • Se pare că acest lucru nu face decât să ceară probleme atunci când un viitor responsabil cu mentenanța vine și crede că poate schimba ordinea argumentelor (dar evident că nu poate). –  > Por nimeni.
  • @AndrewMedico Sunt de acord… se pare că se poate schimba ordinea argumentelor ca în Python. Singurul lucru pe care îl pot spune despre asta este că vor afla foarte repede când schimbarea ordinii argumentelor strică programul. –  > Por Ray Perea.
  • Eu aș spune că myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2'); este mai bun decât myFunc(arg1='Param1', arg2='Param2');, , deoarece nu are nicio șansă de a păcăli cititorul cu privire la faptul că se întâmplă orice argument cu nume real –  > Por Eric.
  • Modelul propus nu are nimic de-a face cu argumentele numite, ordinea încă mai contează, numele nu trebuie să rămână sincronizate cu parametrii reali, spațiul de nume este poluat cu variabile inutile și se implică relații care nu există. –  > Por Dmitri Zaitsev.
dav_i

Dacă doriți să clarificați ce este fiecare parametru, în loc să apelați pur și simplu

someFunction(70, 115);

de ce nu faceți următoarele

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

sigur, este o linie de cod în plus, dar câștigă la capitolul lizibilitate.

Comentarii

  • +1 pentru respectarea principiului KISS, în plus ajută și la depanare. Cu toate acestea, cred că fiecare var ar trebui să fie pe propria linie, deși cu o ușoară lovitură de performanță (http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance). –  > Por clairestreb.
  • 28

  • Nu este vorba doar de linia de cod în plus, ci și de ordinea argumentelor și de a le face opționale. Astfel, ați putea scrie acest lucru cu parametri numiți: someFunction(height: 115);, , dar dacă scrieți someFunction(height); setați de fapt lățimea. –  > Por Francisco Presencia.
  • În acest caz, CoffeeScript acceptă argumente numite. Acesta vă va permite să scrieți doar someFunction(width = 70, height = 115);. Variabilele sunt declarate în partea de sus a domeniului de aplicare curent în codul JavaScript care este generat. –  > Por Audun Olsen.
Udo Klein

O altă modalitate ar fi utilizarea atributelor unui obiect adecvat, de exemplu, în felul următor:

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

Desigur, pentru acest exemplu inventat, este oarecum lipsit de sens. Dar în cazurile în care funcția arată ca

do_something(some_connection_handle, some_context_parameter, some_value)

ar putea fi mai utilă. De asemenea, ar putea fi combinată cu ideea „parameterfy” pentru a crea un astfel de obiect dintr-o funcție existentă într-un mod generic. Adică, pentru fiecare parametru, s-ar crea un membru care poate fi evaluat la o versiune evaluată parțial a funcției.

Această idee este, desigur, legată de Schönfinkeling aka Currying.

Comentarii

  • Aceasta este o idee bună și devine și mai bună dacă o combinați cu trucurile de introspecție a argumentelor. Din păcate, nu reușește să lucreze cu argumente opționale –  > Por Eric.
Allen Ellison

Există o altă cale. Dacă treceți un obiect prin referință, proprietățile acelui obiect vor apărea în domeniul local al funcției. Știu că acest lucru funcționează pentru Safari (nu am verificat alte browsere) și nu știu dacă această funcție are un nume, dar exemplul de mai jos ilustrează utilizarea sa.

Deși, în practică, nu cred că acest lucru oferă vreo valoare funcțională în afara tehnicii pe care o utilizați deja, este puțin mai curat din punct de vedere semantic. Și necesită în continuare transmiterea unei referințe de obiect sau a unui literal de obiect.

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

jgran

Încercând Node-6.4.0 ( process.versions.v8 = ‘5.0.71.60’) și Node Chakracore-v7.0.0-pre8 și apoi Chrome-52 (V8=5.2.361.49), am observat că parametrii numiți sunt aproape implementați, dar această ordine are încă prioritate. Nu găsesc ce spune standardul 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

Dar ordinea este obligatorie!!! Este acesta comportamentul standard?

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

Comentarii

  • Asta nu face ceea ce credeți dumneavoastră. Rezultatul b=10 expresie este 10, , și asta este ceea ce este transmis funcției. f(bla=10) ar funcționa de asemenea (atribuie 10 variabilei bla și trece apoi valoarea către funcție). –  > Por Matthias Kestenholz.
  • De asemenea, se creează a și b variabile în domeniul global ca efect secundar. –  > Por Gershy.
Dmitri Zaitsev

Apelarea funcției f cu parametrii numiți trecuți ca obiect

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

înseamnă practic apelarea compoziției sale f(...g(o)) în cazul în care folosesc sintaxa de răspândire și g este o hartă de „legare” care conectează valorile obiectului cu pozițiile parametrilor lor.

Harta de legare este tocmai ingredientul care lipsește, care poate fi reprezentat de matricea cheilor sale:

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

Observați că trei ingrediente decuplate: funcția, obiectul cu argumente numite și legătura. Această decuplare permite o utilizare foarte flexibilă a acestei construcții, în care legătura poate fi personalizată în mod arbitrar în definirea funcției și extinsă în mod arbitrar în momentul apelării funcției.

De exemplu, este posibil să doriți să abreviați height și width ca h și w în interiorul definiției funcției dumneavoastră, pentru a o face mai scurtă și mai curată, în timp ce doriți în continuare să o apelați cu nume complete pentru claritate:

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

Această flexibilitate este, de asemenea, mai utilă pentru programarea funcțională, unde funcțiile pot fi transformate în mod arbitrar, fără ca numele parametrilor originali să se piardă.

Vince Spicer

Venind din Python, acest lucru m-a deranjat. Am scris un wrapper/Proxy simplu pentru node care va accepta atât obiecte poziționale, cât și obiecte cu cuvinte cheie.

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

Comentarii

  • Dacă înțeleg corect, soluția dvs. necesită să se facă distincția între parametrii poziționali și cei numiți în definiția funcției, ceea ce înseamnă că nu am libertatea de a face această alegere în momentul apelului. –  > Por Dmitri Zaitsev.
  • @DmitriZaitsev: În comunitatea Python (cu argumente de cuvinte cheie fiind o caracteristică de zi cu zi), considerăm că este o idee foarte bună să să putem de a forța utilizatorii să specifice argumentele opționale prin cuvânt cheie; acest lucru ajută la evitarea erorilor. –  > Por Tobias.
  • @Tobias A forța utilizatorii să facă acest lucru în JS este o problemă rezolvată: f(x, opt), , unde opt este un obiect. Dacă ajută la evitarea sau la crearea de erori (cum ar fi cele cauzate de greșelile de ortografie și de durerea de a reține numele cuvintelor cheie) rămâne o întrebare. –  > Por Dmitri Zaitsev.
  • @DmitriZaitsev: În Python acest lucru este strict evită erorile, deoarece (desigur) argumentele cuvintelor cheie sunt o caracteristică de bază a limbajului acolo. Pentru cuvântul cheie-numai argumente, Python 3 are o sintaxă specială (în timp ce în Python 2 ar trebui să scoateți cheile din kwargs dict una câte una și în final se ridică un TypeError dacă rămân chei necunoscute). f(x, opt) soluție permite f să facă ceva asemănător cu ceea ce se întâmplă în Python 2, dar va trebui să vă ocupați singur de toate valorile implicite. –  > Por Tobias.
  • @Tobias Este această propunere relevantă pentru JS? Se pare că descrie modul în care f(x, opt) funcționează deja, în timp ce nu văd cum ajută la răspunsul la întrebarea în care, de exemplu, doriți să faceți atât request(url) și request({url: url}) ceea ce nu ar fi posibil făcând url un parametru doar pentru cuvinte cheie. –  > Por Dmitri Zaitsev.
user234461

Contrar a ceea ce se crede în mod obișnuit, parametrii numiți pot fi implementați în JavaScript standard, de modă veche (doar pentru parametrii booleeni) prin intermediul unei convenții de codare simple și îngrijite, așa cum se arată mai jos.

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

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

Atenționări:

  • Ordinea argumentelor trebuie păstrată – dar modelul este totuși util, deoarece face evident ce argument real este destinat unui parametru formal, fără a fi nevoie să căutați semnătura funcției sau să folosiți un IDE.

  • Acest lucru funcționează numai pentru booleeni. Cu toate acestea, sunt sigur că un model similar ar putea fi dezvoltat pentru alte tipuri, folosind semantica unică de coerciție a tipurilor din JavaScript.

Comentarii

  • Aici vă referiți în continuare prin poziție, nu prin nume. –  > Por Dmitri Zaitsev.
  • @DmitriZaitsev da, chiar am spus acest lucru mai sus. Cu toate acestea, scopul argumentelor cu nume este de a clarifica cititorului ce înseamnă fiecare argument; este o formă de documentare. Soluția mea permite ca documentația să fie încorporată în apelul de funcție fără a recurge la comentarii, care arată dezordonat. –  > Por user234461.
  • Aceasta rezolvă o problemă diferită. Întrebarea se referea la trecerea height după nume, indiferent de ordinea parametrilor. –  > Por Dmitri Zaitsev.
  • @DmitriZaitsev de fapt, întrebarea nu spunea nimic despre ordinea parametrilor, sau de ce OP a vrut să folosească parametrii numiți. –  > Por user234461.
  • O face făcând referire la C#, unde trecerea parametrilor prin nume în orice ordine este cheia. –  > Por Dmitri Zaitsev.