ES6 – default + rest + spread

javascript

Biorąc pod uwagę całe lata narzekań, ECMAScript 2015 przynosi nam wiele nowości, czego rezultatem jest spora ilość usprawnień. Sprawiają one, że pisanie kodu w JavaScript jest bardziej intuicyjne oraz po prostu szybsze. Spójrzmy zatem na nowy sposób przekazywania parametrów do funkcji.


Zobacz całą serię: Let-s talk about ECMAScript 2015


default

Jest to małe, ale bardzo przydatne usprawnienie w przekazywaniu parametrów do funkcji. Wiemy przecież, że funkcje w języku JavaScript pozwalają na przekazanie do nich dowolnej ilości parametrów. Z pewnością nie raz spotkaliście się z podobnym kodem:

function inc(number, increment) {
  // set default to 1 if increment not passed
  // (or passed as undefined)
  increment = increment || 1;
  return number + increment;
}
console.log(inc(2, 2)); // 4
console.log(inc(2));    // 3

Operator logiczny OR (||) przypisuje w tym wypadku wartość 1 do zmiennej increment, ponieważ lewy człon wyrażenia to undefined, czyli false.

ES6 pozwoli pozbyć się tego typu wyrażeń, wykorzystując w tym celu parametry z domyślnymi wartościami. Funkcja inc z wykorzystaniem składni ES6 prezentuje się zatem następująco:

function inc(number, increment = 1) {
  return number + increment;
}
console.log(inc(2, 2)); // 4
console.log(inc(2));    // 3

Tym samym parametry z wartościami domyślnymi są opcjonalne.

Możemy również definiować domyślne wartości parametrów, które występują w środku sygnatury funkcji:

function sum(a, b = 2, c) {
  return a + b + c;
}
console.log(sum(1, 5, 10));         // 16 -> b === 5
console.log(sum(1, undefined, 10)); // 13 -> b as default

Wykorzystanie wartości domyślnej następuje w tym wypadku w momencie, gdy jako wartość parametru podamy undefined.

Wartości domyślne nie muszą być typami prymitywnymi. Jako domyślną wartość możemy przypisać choćby rezultat wykonania funkcji:

function getDefaultIncrement() {
  return 1;
}
function inc(number, increment = getDefaultIncrement()) {
  return number + increment;
}
console.log(inc(2, 2)); // 4
console.log(inc(2));    // 3

rest

Spróbujmy napisać wcześniejszą funkcję sum tak, by brała pod uwagę wszystkie parametry jakie do niej przekażemy. By zachować czystość kodu, pominiemy tutaj walidację. Jeżeli próbowalibyśmy użyć do tego zadania składni dobrze znanej z ES5, to z pewnością otrzymalibyśmy coś takiego:

function sum() {
   var numbers = Array.prototype.slice.call(arguments),
       result = 0;
   numbers.forEach(function (number) {
       result += number;
   });
   return result;
}
console.log(sum(1));             // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

Przy takim podejściu nie wiemy od razu co robi dana funkcja. Nie wystarczy, że spojrzymy na jej sygnaturę. Musimy jeszcze przeskanować jej ciało, znaleźć obiekt arguments i wywnioskować, że chodzi tutaj o zsumowanie wszystkich parametrów przekazanych do funkcji.

Składnia ES6 znacznie lepiej radzi sobie z podobnym problemem . Wprowadza ona pojęcie rest parameters, gdzie parametr poprzedzony (trzema kropkami) staje się tablicą, do której “wpadają” wszystkie pozostałe parametry przekazane do funkcji.

Obiekt arguments zawierać będzie wszystkie parametry funkcji - te nazwane i nie.

Możemy użyć nowej składni ECMAScript 2015 i przepisać funkcję sum:

function sum(…numbers) {
  var result = 0;
  numbers.forEach(function (number) {
    result += number;
  });
  return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

Jest jednak jedno ALE: nie możemy posiadać więcej niż jednego parametru typu …rest. W przeciwnym razie otrzymamy błąd jak niżej:

function sum(…numbers, last) { // causes a syntax error
  var result = 0;
  numbers.forEach(function (number) {
    result += number;
  });
  return result;
}

spread

Kolejne wyrażenie, to spread, które przypomina trochę konstrukcję rest ze względu na swoją notację z trzema kropkami. Dzieli tablicę na poszczególne wartości, które przekazywane są jako osobne parametry do ciała funkcji.

Po raz kolejny zdefiniujmy funkcję sum, do której przekażemy wyrażenie typu spread:

function sum(a, b, c) {
  return a + b + c;
}
var args = [1, 2, 3];
console.log(sum(…args)); // 6

Odpowiednik takiego kodu w ES5, to:

function sum(a, b, c) {
  return a + b + c;
}
var args = [1, 2, 3];
console.log(sum.apply(undefined, args)); // 6

Zamiast więc używać funkcji apply możemy od teraz posługiwać się “rozbiciem” tablicy na osobne parametry. Rozwiązanie prostsze i według mnie również bardziej czytelne.

Ciekawostką jest fakt, że wyrażenie spread możemy łączyć ze standardowym sposobem przekazywania parametru do funkcji:

function sum(a, b, c) {
  return a + b + c;
}
var args = [1, 2];
console.log(sum(…args, 3)); // 6

Rezultat jest dokładnie taki sam jak w poprzednim przykładzie. Tym razem tablica z dwiema wartościami zostaje rozbita na dwa parametry odpowiadające ab, zaś trzecim parametrem jest liczba 3, która odpowiada zmiennej c.

  • kapke

    Gdzie znajdę informację, że “rest” może być używane zamiennie z “named”? Nazwane argumenty występują np. w Pythonie i tam wygląda to tak:
    `info(spacing=15, object=odbchelper)`

    Chyba, że jakiś wątek nie został pociągnięty do końca? Chyba w artykule na MDN było pokazane jak z wykorzystaniem rozbijania i właśnie rest parameters zasymulować nazwane argumenty. Ale wciąż – zasymulować.

    • Dzięki. Masz oczywiście w 100% rację. Użyłem trochę skrótu myślowego, niż faktycznie chodziło mi o parametry nazwane. Poprawię nieco tekst, by nie było niedomówień w tym temacie :)

  • Usuri

    W ostatnim akapicie powinno chyba być “Tym razem tablica z dwiema wartościami” zamiast,
    “Tym razem funkcja z dwiema wartościami”.