Menu

Rest i spread w JavaScript: operator ... wyjaśniony

Jak działa operator ... w JavaScript: zbieranie argumentów przez parametry rest, rozwijanie tablic i obiektów przez spread oraz kiedy używać którego.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Ta sama składnia, dwa zadania

Trzy kropki ... pojawiają się w wielu miejscach nowoczesnego kodu JavaScript i robią dwie przeciwne rzeczy, zależnie od tego, gdzie stoją. Gdy dostrzeżesz schemat, każde użycie ... trafi do jednej z dwóch szufladek:

  • Rest: ...name po stronie odbierającej zbiera wiele wartości w jedną tablicę lub obiekt.
  • Spread: ...value po stronie dającej rozwija tablicę lub obiekt na poszczególne elementy.

To cały model myślowy. Reszta strony to po prostu przykłady obu przypadków i wzorce, do których będziesz wracać.

Parametry rest: zbieranie argumentów

Parametr rest w definicji funkcji zbiera dowolną liczbę argumentów w prawdziwą tablicę:

nums to zwykła tablica. Możesz wywołać na niej .map, .filter, sprawdzić jej .length, przekazać ją do innej funkcji: wszystko, co potrafią tablice.

Parametry rest można łączyć ze zwykłymi parametrami, ale parametr rest musi być ostatni:

label przejmuje pierwszy argument, a wszystko po nim trafia do items. Umieszczenie parametru rest gdziekolwiek indziej niż na końcu to błąd składni.

Rest a stary obiekt arguments

Starszy kod JavaScript używa w zwykłych funkcjach magicznej zmiennej arguments. Wygląda jak tablica, ale nią nie jest, więc metody tablic nie działają na niej bezpośrednio. Parametry rest czysto ją zastępują:

Funkcje strzałkowe w ogóle nie mają obiektu arguments, więc parametry rest to jedyny sposób, żeby przyjmowały zmienną liczbę argumentów. W nowym kodzie wybieraj ...args.

Spread w wywołaniach funkcji

Spread działa odwrotnie: bierze tablicę i rozpakowuje ją na pojedyncze argumenty w miejscu wywołania.

Math.max przyjmuje pojedyncze liczby, a nie tablicę. Przed spreadem pisało się Math.max.apply(null, nums). Teraz wystarczy ... i gotowe.

Zauważ, że dokładnie to samo ... jest rest w definicji funkcji i spread w jej wywołaniu: pozycja mówi ci, z którym masz do czynienia.

Spread w literałach tablic

Rozwinięcie w literale tablicy kopiuje albo łączy tablice:

[...a] daje nową tablicę z tymi samymi elementami. Przydaje się, gdy chcesz sortować albo zmieniać dane bez ruszania oryginału:

scores pozostaje nietknięte, bo .sort działało na kopii. Mały nawyk, a duży zysk, gdy piszesz kod, który nie powinien mieć zaskakujących efektów ubocznych.

Spread w literałach obiektów

Spread działa też na zwykłych obiektach i scala ich właściwości w nowy obiekt:

Późniejsze klucze wygrywają. updates.age nadpisuje user.age, a city dochodzi przy okazji. Kolejność rozwinięć decyduje o wyniku, więc pamiętaj o tym, gdy nakładasz wartości domyślne i nadpisania:

Najpierw wartości domyślne, potem wybory użytkownika. Użytkownik wygrywa przy fontSize, a theme dziedziczy.

Pułapka płytkiej kopii

Spread kopiuje jeden poziom w głąb. Zagnieżdżone obiekty i tablice nadal są współdzielone przez oryginał i kopię:

Obie tablice pokazują nowy tag, bo copy.tags i original.tags to ta sama tablica. Spread nie sklonował zagnieżdżonej listy, tylko skopiował referencję.

Do prawdziwej głębokiej kopii zwykłych danych sięgnij po structuredClone:

Teraz obie tablice są niezależne. structuredClone jest wbudowane w nowoczesne przeglądarki i Node, radzi sobie z zagnieżdżonymi strukturami i jest właściwym wyborem zawsze, gdy płytka kopia nie wystarcza.

Rest w destrukturyzacji

Rest działa też w destrukturyzacji, gdzie zbiera pozostałe elementy lub właściwości:

Wyciągnięcie kilku pól i zachowanie reszty w jednym obiekcie to częsty wzorzec przy przekazywaniu propsów, usuwaniu wrażliwych pól albo budowaniu poprawionych wersji danych:

password zostaje wyciągnięte (i zignorowane), a safe zawiera całą resztę. Bez mutacji i bez ręcznego kopiowania.

Szybkie podsumowanie

  • ...name na liście parametrów albo we wzorcu destrukturyzacji to rest: zbiera.
  • ...value w wywołaniu funkcji, literale tablicy albo literale obiektu to spread: rozwija.
  • Kopie przez spread są płytkie. Zagnieżdżone struktury pozostają współdzielone. Do głębokich kopii używaj structuredClone.
  • Parametry rest to prawdziwe tablice: używaj ich zamiast arguments.
  • W literałach obiektów późniejsze rozwinięcia nadpisują wcześniejsze. Tak buduje się wartości domyślne z nadpisaniami.

Dalej: domknięcia

Funkcje w JavaScript nie tylko przyjmują dane i zwracają wyniki. Pamiętają też zakres, w którym zostały zdefiniowane. Ta pamięć nazywa się domknięciem (closure) i to ona stoi za callbackami, fabrykami i większością wzorców, które poznasz na następnej stronie.

Najczęściej zadawane pytania

Czym różni się rest od spread w JavaScript?

Używają tej samej składni ..., ale robią przeciwne rzeczy. Rest zbiera wiele wartości w jedną tablicę i pojawia się w listach parametrów funkcji oraz w destrukturyzacji. Spread rozwija obiekt iterowalny albo obiekt na poszczególne elementy i pojawia się w wywołaniach funkcji, literałach tablic i literałach obiektów. Jeśli ... stoi po stronie odbierającej, to rest. Jeśli po stronie dającej, to spread.

Jak działają parametry rest w funkcji?

Parametr rest, taki jak w function sum(...nums), zbiera każdy argument przekazany do funkcji w prawdziwą tablicę o nazwie nums. Musi być ostatnim parametrem na liście. W przeciwieństwie do starszego obiektu arguments parametr rest to prawdziwa tablica, więc .map, .filter i .reduce działają na nim bezpośrednio.

Czy operator spread tworzy głęboką kopię?

Nie. Spread kopiuje tylko jeden poziom, czyli tworzy płytką kopię. { ...user } daje nowy obiekt z tymi samymi kluczami najwyższego poziomu, ale zagnieżdżone obiekty i tablice nadal są współdzielonymi referencjami. Do głębokiej kopii użyj structuredClone(value) albo, dla zwykłych danych, serializacji przez JSON.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ