Czy istnieje sposób na podanie nazwanych parametrów w wywołaniu funkcji w JavaScript?

207

Uważam, że funkcja nazwanych parametrów w języku C # jest dość przydatna w niektórych przypadkach.

calculateBMI(70, height: 175);

Czego mogę użyć, jeśli chcę tego w JavaScript?


Nie chcę tego:

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
}

Tego podejścia już użyłem. Czy jest inny sposób?

Nie przeszkadza mi używanie żadnej biblioteki.

Robin Maben
źródło
Nie sądzę, że jest to możliwe, ale możesz spróbować umieścić niektóre niezdefiniowane w pustych miejscach. Co jest bardzo złe. Użyj obiektu, jego dobra.
Vladislav Qulin,
14
Nie, JavaScript / EcmaScript nie obsługują nazwanych parametrów. Przepraszam.
smilly92,
1
Już to wiem. Dzięki. Szukałem jakiegoś sposobu, który polega na dopracowaniu tego, co Functionmoże zrobić istniejący w javascript.
Robin Maben
1
Istniejące Functionw Javascript nie mogą zmienić podstawowej składni Javascript
Gareth
2
Nie sądzę, że Javascript obsługuje tę funkcję. Myślę, że najbliższe do nazwanych parametrów jest (1) dodanie komentarza calculateBMI(70, /*height:*/ 175);, (2) podanie obiektu calculateBMI(70, {height: 175})lub (3) użycie stałej const height = 175; calculateBMI(70, height);.
tfmontague

Odpowiedzi:

211

ES2015 i nowsze

W ES2015 destrukcji parametrów można użyć do symulacji nazwanych parametrów. Wymagałoby to od obiektu wywołującego przekazania obiektu, ale można uniknąć wszystkich kontroli wewnątrz funkcji, jeśli zostaną użyte również parametry domyślne:

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

Istnieje sposób, aby zbliżyć się do tego, co chcesz, ale opiera się on na Function.prototype.toString wynikach [ES5] , który jest w pewnym stopniu zależny od implementacji, więc może nie być kompatybilny z różnymi przeglądarkami.

Chodzi o to, aby parsować nazwy parametrów z reprezentacji ciągu funkcji, aby można było powiązać właściwości obiektu z odpowiednim parametrem.

Wywołanie funkcji mogłoby wtedy wyglądać

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

gdzie ai bsą argumentami pozycyjnymi, a ostatni argument jest obiektem o nazwanych argumentach.

Na przykład:

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

Które byś użył jako:

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 

PRÓBNY

Istnieje kilka wad tego podejścia (zostałeś ostrzeżony!):

  • Jeśli ostatnim argumentem jest obiekt, jest on traktowany jako „nazwany obiekt argumentu”
  • Zawsze dostaniesz tyle argumentów, ile zdefiniowałeś w funkcji, ale niektóre z nich mogą mieć wartość undefined(która różni się od braku żadnej wartości). Oznacza to, że nie można użyć arguments.lengthdo sprawdzenia, ile argumentów zostało przekazanych.

Zamiast mieć funkcję tworzącą opakowanie, możesz również mieć funkcję, która akceptuje funkcję i różne wartości jako argumenty, takie jak

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

lub nawet rozszerzyć Function.prototype, abyś mógł:

foo.execute(a, b, {posArg: ...});
Felix Kling
źródło
Tak ... oto przykład tego: jsfiddle.net/9U328/1 (chociaż powinieneś raczej użyć Object.definePropertyi ustawić enumerablena false). Zawsze należy zachować ostrożność przy rozszerzaniu obiektów rodzimych. Całe podejście wydaje się nieco zuchwałe, więc nie spodziewałbym się, że zadziała teraz i na zawsze;)
Felix Kling
Odnotowany. Zacznę od użycia tego. Oznaczenie jako odpowiedź! ... na razie;)
Robin Maben
1
Bardzo drobny nitpick: Nie sądzę, że to podejście uchwyci funkcje strzałek EcmaScript 6 . Obecnie nie jest to duży problem, ale warto o tym wspomnieć w części ostrzeżeń w odpowiedzi.
NobodyMan
3
@NobodyMan: True. Napisałem tę odpowiedź, zanim funkcje strzałek były rzeczą. W ES6 faktycznie skorzystałbym z destrukcji parametrów.
Felix Kling
1
W kwestii undefinedvs „brak wartości” można dodać, że dokładnie tak działają domyślne wartości funkcji JS - traktując to undefinedjako wartość brakującą.
Dmitri Zaitsev
71

Nie - podejście obiektowe jest odpowiedzią JavaScript na to. Nie ma z tym problemu, pod warunkiem, że twoja funkcja oczekuje obiektu, a nie oddzielnych parametrów.

Mitya
źródło
35
@RobertMaben - odpowiedź na konkretne zadane pytanie jest taka, że ​​nie ma natywnego sposobu zbierania deklarowanych zmiennych lub funkcji bez wiedzy, że żyją w określonej przestrzeni nazw. Tylko dlatego, że odpowiedź jest krótka, nie neguje jej przydatności jako odpowiedzi - nie zgadzasz się? Tam są znacznie krótsze odpowiedzi, w stylu „nie, nie jest możliwe”. Mogą być krótkie, ale są również odpowiedzią na pytanie.
Mitya,
3
Obecnie jest jednak es6: 2ality.com/2011/11/keyword-parameters.html
slacktracer
1
To zdecydowanie uczciwa odpowiedź - „nazwane parametry” to funkcja językowa. Podejście obiektowe jest kolejną najlepszą rzeczą w przypadku braku takiej funkcji.
fatuhoku,
32

Ta kwestia od jakiegoś czasu była moim wkurzeniem. Jestem doświadczonym programistą z wieloma językami. Jednym z moich ulubionych języków, z których miałem przyjemność korzystać, jest Python. Python obsługuje nazwane parametry bez żadnych sztuczek ... Odkąd zacząłem używać Pythona (jakiś czas temu) wszystko stało się łatwiejsze. Uważam, że każdy język powinien obsługiwać nazwane parametry, ale tak nie jest.

Wiele osób mówi, aby po prostu użyć sztuczki „Przekaż obiekt”, aby nazwać parametry.

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

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

I to działa świetnie, z wyjątkiem ..... jeśli chodzi o większość IDE dostępnych na rynku, wielu programistów korzysta z podpowiedzi typu / argumentów w naszym IDE. Osobiście używam PHP Storm (wraz z innymi IDE JetBrains, takimi jak PyCharm dla Pythona i AppCode dla Celu C)

Największy problem z użyciem sztuczki „Przekaż obiekt” polega na tym, że podczas wywoływania funkcji IDE daje ci wskazówkę dotyczącą jednego typu i to jest to ... Skąd mamy wiedzieć, jakie parametry i typy powinny wchodzić do obiekt arg1?

Nie mam pojęcia, jakie parametry powinny przejść w arg1

Więc ... sztuczka „Przekaż obiekt” nie działa dla mnie ... W rzeczywistości powoduje więcej problemów z koniecznością spojrzenia na docblock każdej funkcji, zanim dowiem się, jakich parametrów oczekuje funkcja ... Jasne, świetnie nadaje się do kiedy utrzymujesz istniejący kod, ale pisanie nowego kodu jest okropne.

Cóż, to jest technika, której używam ... Teraz mogą być z tym pewne problemy, a niektórzy programiści mogą powiedzieć mi, że robię to źle, i mam otwarty umysł, jeśli chodzi o te rzeczy ... Zawsze chętnie szukam lepszych sposobów wykonania zadania ... Jeśli więc jest problem z tą techniką, komentarze są mile widziane.

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

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

W ten sposób mam to, co najlepsze z obu światów ... nowy kod jest łatwy do napisania, ponieważ moje IDE daje mi wszystkie odpowiednie wskazówki argumentów ... A przy zachowaniu kodu później widzę nie tylko wartość przekazana do funkcji, ale także nazwa argumentu. Jedyny narzut, jaki widzę, to zadeklarowanie nazw argumentów jako zmiennych lokalnych, aby zapobiec zanieczyszczeniu globalnej przestrzeni nazw. Jasne, to trochę dodatkowego pisania, ale banalne w porównaniu do czasu potrzebnego na wyszukiwanie bloków dokumentów podczas pisania nowego kodu lub utrzymywania istniejącego kodu.

Teraz mam wszystkie parametry i typy podczas tworzenia nowego kodu

Ray Perea
źródło
2
Jedyną rzeczą w tej technice jest to, że nie można zmienić kolejności parametrów ... Ale osobiście mi to odpowiada.
Ray Perea,
13
Wygląda na to, że po prostu prosi o kłopoty, gdy pojawia się jakiś przyszły opiekun i myśli, że mogą zmienić kolejność argumentów (ale oczywiście nie mogą).
nikt
1
@AndrewMedico Zgadzam się ... wygląda na to, że możesz po prostu zmienić kolejność argumentów jak w Pythonie. Jedyne, co mogę o tym powiedzieć, to to, że dowiedzą się naprawdę szybko, gdy zmiana kolejności argumentów spowoduje uszkodzenie programu.
Ray Perea,
7
Twierdzę, że myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');jest to lepsze niż myFunc(arg1='Param1', arg2='Param2');, ponieważ nie ma szansy na oszukanie czytelnika, niż w przypadku faktycznych nazwanych argumentów
Eric
2
Proponowany wzorzec nie ma nic wspólnego z nazwanymi argumentami, kolejność wciąż ma znaczenie, nazwy nie muszą być zsynchronizowane z rzeczywistymi parametrami, przestrzeń nazw jest zanieczyszczona niepotrzebnymi zmiennymi i sugerowane są relacje, których nie ma.
Dmitri Zaitsev
25

Jeśli chcesz wyjaśnić, jakie są parametry, zamiast wywoływania

someFunction(70, 115);

dlaczego nie wykonać następujących czynności

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

pewnie, to dodatkowy wiersz kodu, ale wygrywa pod względem czytelności.

dav_i
źródło
5
+1 za przestrzeganie zasady KISS, a także pomaga w debugowaniu. Myślę jednak, że każda zmienna powinna mieć swoją własną linię, choć z niewielkim spadkiem wydajności ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb,
21
Nie chodzi tylko o dodatkowy wiersz kodu, ale także o kolejność argumentów i ich opcjonalność. Możesz więc napisać to z nazwanymi parametrami: someFunction(height: 115);ale jeśli piszesz someFunction(height);, tak naprawdę ustawiasz szerokość.
Francisco Presencia
W takim przypadku CoffeeScript obsługuje nazwane argumenty. Pozwoli ci to pisać someFunction(width = 70, height = 115);. Zmienne są deklarowane u góry bieżącego zakresu w generowanym kodzie JavaScript.
Audun Olsen
6

Innym sposobem byłoby użycie atrybutów odpowiedniego obiektu, np .:

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

Oczywiście dla tego wymyślonego przykładu jest on nieco bez znaczenia. Ale w przypadkach, w których wygląda funkcja

do_something(some_connection_handle, some_context_parameter, some_value)

to może być bardziej przydatne. Można go również połączyć z pomysłem „parametryzuj”, aby utworzyć taki obiekt z istniejącej funkcji w ogólny sposób. Oznacza to, że dla każdego parametru utworzyłby element członkowski, który może ewaluować do częściowo ocenionej wersji funkcji.

Pomysł ten jest oczywiście związany z Schönfinkeling aka Currying.

Udo Klein
źródło
To fajny pomysł i staje się jeszcze lepszy, jeśli połączysz go z argumentami sztuczek introspekcji. Niestety nie działa z opcjonalnymi argumentami
Eric
2

Jest inny sposób. Jeśli przekazujesz obiekt przez odniesienie, właściwości tego obiektu pojawią się w lokalnym zasięgu funkcji. Wiem, że to działa w przeglądarce Safari (nie sprawdziłem innych przeglądarek) i nie wiem, czy ta funkcja ma nazwę, ale poniższy przykład ilustruje jej użycie.

Chociaż w praktyce nie sądzę, że oferuje to jakąkolwiek wartość funkcjonalną poza techniką, której już używasz, jest nieco czystsza semantycznie. I wciąż wymaga przekazania odwołania do obiektu lub dosłowności do obiektu.

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
źródło
2

Próbując Node-6.4.0 (process.versions.v8 = „5.0.71.60”) i Node Chakracore-v7.0.0-pre8, a następnie Chrome-52 (V8 = 5.2.361.49), zauważyłem, że nazwane parametry są prawie zaimplementowano, ale ta kolejność ma nadal pierwszeństwo. Nie mogę znaleźć tego, co mówi 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

Ale zamówienie jest wymagane !! Czy to standardowe zachowanie?

> f(b=10)
a=10 + b=2 = 12
jgran
źródło
10
To nie robi tego, co myślisz. Wynikiem tego b=10wyrażenia jest 10i to jest przekazywane do funkcji. f(bla=10)również by działał (przypisuje 10 do zmiennej, blaa następnie przekazuje wartość do funkcji).
Matthias Kestenholz
1
Jest to również tworzenie ai bzmienne w zakresie globalnym jako efekt uboczny.
Gershom,
2

Wywołanie funkcji fz nazwanymi parametrami przekazanymi jako obiekt

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

w zasadzie nazywa swoją kompozycję, w f(...g(o))której używam składni rozprzestrzeniania ig jest mapą „wiążącą” łączącą wartości obiektów z ich pozycjami parametrów.

Mapa powiązań jest właśnie brakującym składnikiem, który może być reprezentowany przez tablicę jej kluczy:

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

Zwróć uwagę na trzy oddzielone składniki: funkcję, obiekt z nazwanymi argumentami i powiązanie. To oddzielenie pozwala na dużą elastyczność w korzystaniu z tej konstrukcji, gdzie powiązanie można dowolnie dostosować w definicji funkcji i dowolnie rozszerzyć w czasie wywołania funkcji.

Na przykład możesz chcieć skrócić heighti widthjako hi wwewnątrz definicji funkcji, aby była krótsza i bardziej przejrzysta, podczas gdy nadal chcesz wywoływać ją z pełnymi nazwami dla jasności:

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

Ta elastyczność jest również bardziej użyteczna w programowaniu funkcjonalnym, w którym funkcje można dowolnie przekształcać, gubiąc oryginalne nazwy parametrów.

Dmitri Zaitsev
źródło
0

Pochodzące z Pythona to mnie wkurzyło. Napisałem prosty wrapper / proxy dla węzła, który akceptuje zarówno obiekty pozycyjne, jak i słowa kluczowe.

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

Vince Spicer
źródło
Jeśli dobrze rozumiem, twoje rozwiązanie wymaga rozróżnienia parametrów pozycyjnych i nazwanych w definicji funkcji, co oznacza, że ​​nie mam swobody wyboru w czasie połączenia.
Dmitri Zaitsev,
@DmitriZaitsev: W społeczności Pythona (z argumentami słowo jest funkcja codziennie), że to bardzo dobry pomysł, aby rozważyć w stanie zmusić użytkowników do określenia opcjonalnych argumentów według słów kluczowych; pomaga to uniknąć błędów.
Tobias
@Tobias Zmuszanie użytkowników do zrobienia tego w JS to rozwiązany problem: f(x, opt)gdzie optjest obiekt. To, czy pomaga to w uniknięciu błędów, czy w tworzeniu błędów (takich jak błędy ortograficzne i trudności z zapamiętywaniem nazw słów kluczowych), pozostaje pytaniem.
Dmitri Zaitsev,
@DmitriZaitsev: W Pythonie ściśle unika się błędów, ponieważ (oczywiście) argumenty słów kluczowych są tam główną funkcją języka. W przypadku argumentów zawierających tylko słowa kluczowe , Python 3 ma specjalną składnię (podczas gdy w Pythonie 2 wyrzucasz klucze ze kwargs słownika jeden po drugim i ostatecznie podnosisz, TypeErrorjeśli pozostaną nieznane klucze). Twoje f(x, opt)rozwiązanie pozwala tej ffunkcji zrobić coś takiego jak w Pythonie 2, ale sam musisz obsługiwać wszystkie wartości domyślne.
Tobias
@Tobias Czy ta propozycja dotyczy JS? Wygląda na to, że opisuje, jak f(x, opt)to już działa, podczas gdy nie widzę, jak to pomaga odpowiedzieć na pytanie, gdzie np. Chcesz zrobić jedno request(url)i drugie request({url: url}), co nie byłoby możliwe, ustawiając urlparametr tylko dla słowa kluczowego.
Dmitri Zaitsev
-1

Wbrew powszechnemu przekonaniu, nazwane parametry można zaimplementować w standardowym oldskulowym JavaScript (tylko dla parametrów boolowskich) za pomocą prostej, zgrabnej konwencji kodowania, jak pokazano poniżej.

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

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

Ostrzeżenia:

  • Kolejność argumentów musi być zachowana - ale wzorzec jest nadal użyteczny, ponieważ pokazuje, który faktyczny argument jest przeznaczony dla którego parametru formalnego, bez konieczności grepowania dla podpisu funkcji lub używania IDE.

  • Działa to tylko w przypadku wartości logicznych. Jestem jednak pewien, że można opracować podobny wzorzec dla innych typów przy użyciu unikalnej semantyki koercji typów JavaScript.

użytkownik234461
źródło
Nadal odsyłacie według pozycji, a nie nazwy tutaj.
Dmitri Zaitsev,
@DmitriZaitsev tak, powiedziałem nawet powyżej. Jednak celem nazwanych argumentów jest wyjaśnienie czytelnikowi, co oznacza każdy argument; to forma dokumentacji. Moje rozwiązanie umożliwia osadzenie dokumentacji w wywołaniu funkcji bez uciekania się do komentarzy, które wyglądają nieporządnie.
user234461,
To rozwiązuje inny problem. Pytanie dotyczyło podania heightimienia bez względu na porządek param.
Dmitri Zaitsev
@DmitriZaitsev w rzeczywistości pytanie nie mówiło nic o kolejności parametrów ani dlaczego OP chciał używać nazwanych parametrów.
user234461
Dzieje się tak, odwołując się do C #, gdzie kluczem jest przekazywanie parametrów według nazwy w dowolnej kolejności.
Dmitri Zaitsev