Parametry resztowe i domyślne

> Dodaj do ulubionych

ES6 bez tajemnic to cykl artykułów poświęconych nowym składnikom języka JavaScript, które pojawiły się w 6. edycji standardu ECMAScript, w skrócie ES6.

Dzisiejszy artykuł poświęcony jest dwóm składnikom, dzięki którym programiści JavaScriptu będą mogli wyrażać w swoich funkcjach więcej niż dotychczas – to parametry resztowe i parametry domyślne.

Parametry resztowe

Podczas tworzenia interfejsu API zwykle potrzebna jest funkcja wariadyczna (ang. variadic function), czyli funkcja przyjmująca dowolną liczbę argumentów. Na przykład metoda String.prototype.concat może przyjąć dowolną liczbę argumentów łańcuchowych. Wprowadzone w ES6 parametry resztowe umożliwiają pisanie funkcji wariadycznych w nowy sposób.

Zilustruję to zagadnienie, pisząc prostą funkcję wariadyczną o nazwie containsAll, która sprawdzi czy dany łańcuch zawiera określone podłańcuchy. Przykładowo wywołanie funkcji containsAll("banana", "b", "nan") zwróci wartość true, a containsAll("banana", "c", "nan")false.

Oto standardowy sposób na zaimplementowanie funkcji containsAll:

function containsAll(haystack) {
  for (var i = 1; i < arguments.length; i++) {
    var needle = arguments[i];
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

W powyższej implementacji wykorzystywany jest magiczny, przypominający tablicę obiekt arguments, w którym zawarte są parametry przekazywane funkcji. Zaprezentowany kod spełnia swoje zadanie, jednak mógłby być bardziej czytelny. Lista parametrów funkcji zawiera tylko jeden element — haystack. Rzut oka na funkcję nie wystarczy zatem, by stwierdzić, że wbrew pozorom przyjmuje ona kilka argumentów. Ponadto należy pamiętać, by iteracyjne przeglądanie obiektu arguments rozpocząć od indeksu o wartości 1, a nie 0, ponieważ element arguments[0] odpowiada argumentowi haystack. Jeśli natomiast chcielibyśmy dodać jeszcze jeden parametr przed argumentem haystack lub po nim, musielibyśmy również zaktualizować pętlę for. Parametry resztowe rozwiązują oba te problemy. Oto naturalna implementacja funkcji containsAll w ES6, wykorzystująca parametr resztowy:

function containsAll(haystack, ...needles) {
  for (var needle of needles) {
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

Powyższa wersja funkcji działa identycznie jak jej poprzedniczka, jednak zawiera specjalną składnię ...needles. Sprawdźmy jak zadziała dla wywołania containsAll("banana", "b", "nan"). Jak zwykle, argumentowi haystack przypisywany jest pierwszy z przekazanych parametrów, czyli "banana". Wielokropek przed argumentem needles wskazuje, że jest to parametr resztowy. Wszystkie pozostałe przekazywane parametry są umieszczone w tablicy i przypisane zmiennej needles. W naszym przykładowym wywołaniu parametrowi needles przypisana zostaje tablica ["b", "nan"]. Wykonanie funkcji przebiega następnie tak jak zwykle. (Zwróć uwagę, że wykorzystaliśmy pętlę for-of z ES6).

Tylko ostatni parametr funkcji może być oznaczony jako parametr resztowy. Poprzedzające go parametry są przypisywane przy wywołaniu tak jak zwykle. Wszelkie „dodatkowe” argumenty są natomiast umieszczane w tablicy i przypisywane parametrowi resztowemu. W przypadku braku dodatkowych argumentów parametrowi resztowemu przypisana zostaje po prostu pusta tablica, nigdy wartość undefined.

Parametry domyślne

W przypadku funkcji często bywa tak, że przekazanie wszystkich możliwych parametrów przez kod inicjujący wywołanie nie jest wymagane. Istnieją sensowne parametry domyślne, z których można skorzystać w miejsce nieprzekazywanych parametrów. W JavaScripcie od zawsze parametry domyślne są sztywne – wszelkie braki wartości domyślnie zapełnia się wartością undefined. W ES6 pojawia się jednak sposób na określenie dowolnej wartości parametrów domyślnych.

Oto przykład (odwrotne apostrofy oznaczają łańcuchy szablonowe, które omawialiśmy w poprzednim artykule):

function animalSentence(animals2="tygrysy", animals3="niedźwiedzie") {
    return `Lwy i ${animals2} i ${animals3}! O matko!`;
}

Dla każdego parametru po znaku = następuje wyrażenie określające domyślną wartość tego parametru, która zostanie wykorzystana, jeśli nie kod inicjujący wywołanie przekaże czegoś innego. Funkcja animalSentence() zwróci zatem łańcuch "Lwy i tygrysy i niedźwiedzie! O matko!" , funkcja animalSentence("słonie") zwróci łańcuch "Lwy i słonie i niedźwiedzie! O matko!" , a animalSentence("słonie", "wieloryby") zwróci łańcuch "Lwy i słonie i wieloryby! O matko!" .

Istnieje kilka szczególnych cech parametrów domyślnych:

  • W przeciwieństwie do analogicznego rozwiązania w Pythonie, w ES6 wyrażenia o wartości domyślnej są przetwarzane w czasie wywołania funkcji od lewej. Oznacza to również, że wyrażenia domyślne mogą przyjąć wartość przypisanych wcześniej parametrów. Moglibyśmy więc napisać jeszcze bardziej wymyślną funkcję, na przykład:
    function animalSentenceFancy(animals2="tygrysy",
        animals3=(animals2 == "niedzwiedzie") ? "lwy morskie" : "niedzwiedzie")
    {
      return `Lwy i ${animals2} i ${animals3}! O matko!`;
    }

    W takim przypadku funkcja animalSentenceFancy(„niedźwiedzie”) zwróci łańcuch „Lwy i niedźwiedzie i lwy morskie. O matko!”.

  • Przekazanie wartości undefined traktowane jest jak nieprzekazanie niczego, dlatego też wynikiem funkcji animalSentence(undefined, "jednorożce") będzie łańcuch "Lwy i tygrysy i jednorożce! O matko!" .
  • Parametr bez określonej wartości domyślnej automatycznie przyjmuje wartość domyślną undefined, a więc funkcja:
    function myFunc(a=42, b) {...}

    jest dopuszczalna w takiej postaci i jest równoważna z

    function myFunc(a=42, b=undefined) {...}

Koniec z obiektem arguments

Jak zdążyliśmy się przekonać, parametry resztowe i domyślne można stosować zamiast obiektu arguments, a pozbycie się go zwykle sprawia, że kod staje się czytelniejszy. Oprócz negatywnego wpływu na czytelność kodu, jego magiczne właściwości przysparzają także problemów w optymalizacji wirtualnych maszyn JavaScriptu.

Można mieć nadzieję, że parametry resztowe i domyślne zupełnie zastąpią obiekt arguments. Zostały już poczynione pewne kroki w tym kierunku – w funkcjach wykorzystujących parametry resztowe lub domyślne nie można używać obiektu arguments. Obiekt arguments nie przestanie być obsługiwany w najbliższej przyszłości, a może nawet nigdy, lecz preferowane jest, tam gdzie to możliwe, zastępowanie go parametrami resztowymi lub domyślnymi.

Obsługa parametrów resztowych i domyślnych w przeglądarkach

Parametry resztowe i domyślne są obsługiwane w Firefoksie począwszy od wersji 15.

Niestety, póki co jest to jedyna oficjalna przeglądarka obsługująca te składniki ES6. W silniku V8 wprowadzono niedawno eksperymentalną obsługę parametrów resztowych, a aktualnie trwają prace nad implementacją parametrów domyślnych. Próba implementacji parametrów resztowych i domyślnych została również podjęta w komponencie JSC.

Z parametrów domyślnych można jednak zacząć korzystać już teraz, ponieważ są one obsługiwane zarówno w kompilatorze Babel jak i Traceur.

Podsumowanie

Choć teoretycznie parametry resztowe i domyślne nie wzbogacają funkcjonalności kodu, pomagają pisać bardziej ekspresyjne i czytelniejsze deklaracje funkcji JS. Wesołego wywoływania!

Adnotacja: podziękowania dla Benjamina Petersona za zaimplementowanie wymienionych składników w Firefoksie, za cały jego wkład w ten projekt i oczywiście za niniejszy artykuł.

W następnym artykule przedstawimy kolejny prosty, elegancki i praktyczny w codziennym użytku element ES6. Bazuje on na znanej nam składni służącej do pisania tablic i obiektów, która zostaje postawiona na głowie. W rezultacie otrzymujemy nowy, zwięzły sposób na rozkładanie tablic i obiektów na części pierwsze. Co to oznacza? Po co mielibyśmy rozkładać obiekty? Odpowiedź na to pytanie poznasz w kolejnym artykule, w którym inżynier z Mozilli Nick Fitzgerald szczegółowo przedstawi zagadnienie destrukturyzacji w ES6.

Autor: Jason Orendorff

Źródło: https://hacks.mozilla.org/2015/05/es6-in-depth-rest-parameters-and-defaults/

Tłumaczenie: Joanna Liana

Treść tej strony jest dostępna na zasadach licencji CC BY-SA 3.0