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:
...namepo stronie odbierającej zbiera wiele wartości w jedną tablicę lub obiekt. - Spread:
...valuepo 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
...namena liście parametrów albo we wzorcu destrukturyzacji to rest: zbiera....valuew 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.