Moduł3 - Zajęcia 6 - Operacje spread/rest
Section1 Article1: Składnia spread i rest
Współczesny standard wprowadził nową składnię do pracy z iterowalnymi strukturami, takimi jak string, tablica czyli obiekt. Jego funkcjonalność i nazwa zależy od miejsca zastosowania.
Section1 Article2: spread: przekazywanie argumentów
Operacja ... (spread) umożliwia rozłożenie kolekcji elementów (tablicy, stringa lub obiektu) do miejsca, w którym oczekiwany jest zestaw różnych wartości. Istnieją pewne ograniczenia, na przykład nie można rozkładać tablicy do obiektu i odwrotnie.
Wytłumaczmy sobie to na analogii z pudełkiem jabłek. Stawiając pudełko na podłodze bez wyjmowania z niego jabłek, otrzymujemy odpowiednik tablicy wartości. Jeśli wysypać jabłka z pudełka na podłogę, następuje rozłożenie - kolekcja przetwarza się w zestaw oddzielnych wartości.
Istnieje pewna różnica - w JavaScript takie rozłożenie nie zmienia oryginalnej kolekcji, czyli tworzy kopię każdego elementu. Po rozłożeniu będziemy mieli zarówno pudełko pełne jabłek jak i kopię każdego z jabłek.
Dla przykładu, metoda Math.max(argumenty) wyszukuje i zwraca największy z argumentów (liczb), co oznacza, że nie oczekuje tablicy wartości, ale dowolnej ilości argumentów.
const temps = [14, -4, 25, 8, 11];
// Konsola wyświetli tablicę
console.log(temps);
// ❌ To nie zadziała, ponieważ przekazujemy całą tablicę
console.log(Math.max(temps));// NaN
// Konsola wyświetli zestaw oddzielnych liczb
console.log(...temps);
// ✅ Rozkładamy kolekcję elementów na oddzielne argumenty
console.log(Math.max(...temps));// 25
Czyli zapis Math.max(...[14, -4, 25, 8, 11]), po interpretacji operatora ... zmieni się w Math.max(14, -4, 25, 8, 11) - składnia ... zwraca "rozpakowuje" tablicę, czyli rozkłada jej elementy na oddzielne argumenty.
Section1 Article3: spread: tworzenie nowej tablicy
Operacja ... (spread) pozwala również na utworzenie kopii tablicy lub "sklejenie" dowolnej liczby tablic w jedną nową. Wcześniej używaliśmy do tego metod slice() i concat(), ale operacja rozłożenia pozwala zrobić to samo w krótszej formie.
const temps = [14, -4, 25, 8, 11];
// To jest dokładna, niezależna kopia tablicy temps
const copyOfTemps = [...temps];
console.log(copyOfTemps);// [14, -4, 25, 8, 11]
W powyższym przykładzie mamy pudełko "jabłek" temps i chcemy stworzyć jego niezależną kopię. Bierzemy puste pudełko i wsypujemy do niego jabłka z oryginalnego pudełka temps - rozkładamy go do innej kolekcji. W tym przypadku pudełko temps nie zmieni się, nadal będzie zawierało jabłka, a nowe pudełko będzie zawierało ich dokładne kopie. W ten sposób unikamy problemów związanych z referencją.
W poniższym przykładzie wsypujemy jabłka z dwóch pudełek do nowego. Oryginalne pudełka (tablice) nie ulegną zmianie, ale nowe będą zawierały kopie wszystkich ich jabłek (elementów). Kolejność rozkładania jest ważna - wpływa na kolejność elementów w nowej tablicy.
const lastWeekTemps = [14, 25, 11];
const currentWeekTemps = [23, 17, 18];
const allTemps = [...lastWeekTemps, ...currentWeekTemps];
console.log(allTemps);// [14, 25, 11, 23, 17, 18]
Kolejność rozkładania ma znaczenie. Nazwy właściwości obiektów są niepowtarzalne, więc właściwości rozkładanego obiektu mogą nadpisać wartość istniejącej już właściwości, jeśli ich nazwy są takie same.
const first = { propA: 5, propB: 10, propC: 50 };
const second = { propC: 15, propD: 20 };
const third = { ...first, ...second };
console.log(third);// { propA: 5, propB: 10, propC: 15, propD: 20 }
const fourth = { ...second, ...first };
console.log(fourth);// { propA: 5, propB: 10, propC: 50, propD: 20 }
Gdyby jabłka w pudełku miały etykiety, to w jednym pudełku nie mogłyby znajdować się dwa jabłka z tymi samymi etykietami. Dlatego podczas wysypania drugiego pudełka wszystkie jabłka, których etykiety pokrywają się z tymi, które są już w nowym, zastąpią te, które znalazły się już w pudełku.
Podczas rozkładania możesz dodać właściwości w dowolne miejsca. Najważniejszą rzeczą do zapamiętania jest to, że nazwa właściwości jest unikalna i że jej wartość można nadpisać.
const first = { propA: 5, propB: 10, propC: 50 };
const second = { propC: 15 };
const third = { propB: 20, ...first, ...second };
console.log(third);// { propA: 5, propB: 10, propC: 15 }
const fourth = { ...first, ...second, propB: 20 };
console.log(fourth);// { propA: 5, propB: 20, propC: 15 }
const fifth = { ...first, propB: 20, ...second };
console.log(fifth);// { propA: 5, propB: 20, propC: 15 }
Section1 Article4: rest: zbiór wszystkich argumentów funkcji
Operacja ... (rest) umożliwia zebranie grupy niezależnych elementów w nową kolekcję. Syntaktycznie jest to bliźniak operacji rozłożenia, ale łatwo je rozróżnić — rozłożenie następuje, gdy ... znajduje się po prawej stronie operacji przypisania, a zbiór ma miejsce, gdy ... znajduje się po lewej stronie.
Wróćmy do analogii z jabłkami. Jeśli na podłodze są jabłka i mamy puste pudełko, to operacja rest pozwoli nam "zbierać" jabłka do pudełka. W takim przypadku oryginalne jabłka pozostaną na podłodze, a w pudełku będzie kopia każdego jabłka.
Jedno z zastosowań operacji rest znajdziemy podczas tworzenie funkcji, które mogą przyjmować dowolną liczbę argumentów.
// Jak zadeklarować parametry funkcji tak,
// aby można było przekazać dowolną liczbę argumentów?
function multiply() {
// ...
}
multiply(1, 2);
multiply(1, 2, 3);
multiply(1, 2, 3, 4);
Jeśli usuniemy cały "szum" z kodu i spojrzymy na argumenty i parametry funkcji, to argumenty znajdują się po prawej stronie operacji przypisania, a parametry po lewej, ponieważ wartości argumentów są przypisane do zadeklarowanych parametrów. Możesz więc "zebrać" wszystkie argumenty funkcji w jeden parametr za pomocą operacji rest.
function multiply(...args) {
console.log(args);// tablica wszystkich argumentów
}
multiply(1, 2);
multiply(1, 2, 3);
multiply(1, 2, 3, 4);
Nazwa parametru może być dowolna. Najczęściej nazywamy go args, restArgs albo otherArgs, czyli różnymi skrótami od arguments (będącego słowem kluczowym więc nie możemy go tutaj wykorzystać).
Section1 Article5: rest: zbiór części argumentów funkcji
Operacja ... (rest) umożliwia również zbiór do tablicy tylko części argumentów, poprzez zadeklarowanie zdefiniowanych parametrów przed "zbiorem".
function multiply(firstNumber, secondNumber, ...otherArgs) {
console.log(firstNumber);// Wartość pierwszego argumentu
console.log(secondNumber);// Wartość drugiego argumentu
console.log(otherArgs);// Tablica innych argumentów
}
multiply(1, 2);
multiply(1, 2, 3);
multiply(1, 2, 3, 4);
Wszystkie argumenty dla których zostaną zadeklarowane parametry przekażą im swoje wartości, reszta argumentów zostanie umieszczona w tablicy jeśli skorzystamy z tego zapisu. Operacja rest zbiera wszystkie pozostałe argumenty i dlatego musi znaleźć się na końcu w sygnaturze funkcji, w przeciwnym razie wystąpi błąd.
Section2 Article1: Destrukturyzacja obiektów
Podczas działania programów, dane przychodzą zazwyczaj w postaci tablic i obiektów, których wartości potrzebujemy zapisywać w zmiennych lokalnych. Aby to maksymalnie uprościć, współczesny standard udostępnia składnię przypisania destrukturyzującego.
Section2 Article2: Destrukturyzacja obiektów
Złożone dane są zawsze reprezentowane jako obiekt. Wielokrotne wywołania właściwości obiektu wizualnie zanieczyszczają kod.
const book = {
title: "The Last Kingdom",
author: "Bernard Cornwell",
genres: ["historical prose", "adventure"],
isPublic: true,
rating: 8.38,
};
const accessType = book.isPublic ? "publiczny" : "zamknięty";
const message = `Książka ${book.title} autorstwa ${book.author} z oceną ${book.rating} ma dostęp ${accessType}.`
// 'Książka The Last Kingdom autorstwa Bernard Cornwell z oceną 8.38 ma dostęp publiczny.'
Destrukturyzacja umożliwia "rozpakowanie" wartości właściwości obiektu do zmiennych lokalnych. Dzięki temu kod w miejscu ich użycia jest mniej "zaszumiony".
const book = {
title: "The Last Kingdom",
author: "Bernard Cornwell",
genres: ["historical prose", "adventure"],
isPublic: true,
rating: 8.38,
};
// Destrukturyzacja
const { title, author, isPublic, rating, coverImage } = book;
console.log(coverImage);// undefined
const accessType = isPublic ? "publiczny" : "zamknięty";
const message = `Książka ${title} autorstwa ${author} z oceną ${rating} ma dostęp w ${accessType}.`;
// 'Książka The Last Kingdom autorstwa Bernard Cornwell z oceną 8.38 ma dostęp publiczny.'
Destrukturyzacja zawsze znajduje się po lewej stronie operacji przypisania. Zmiennym wewnątrz nawiasów klamrowych przypisywane są wartości właściwości obiektu. Jeśli nazwa zmiennej i nazwa właściwości są takie same, to przypisanie ma miejsce, w przeciwnym razie zostanie mu przypisane undefined. Kolejność, w jakiej zmienne są deklarowane w nawiasach klamrowych, nie jest ważna, ponieważ w obiekcie nazwy właściwości są unikalne.
Section2 Article3: Wartości domyślne
Aby uniknąć przypisania undefined podczas destrukturyzacji nieistniejących właściwości obiektu, możesz ustawić domyślne wartości zmiennych, które zostaną przypisane tylko wtedy, gdy obiekt nie ma właściwości o danej nazwie.
const book = {
title: "The Last Kingdom",
author: "Bernard Cornwell",
};
// Dodajmy zdjęcie na okładkę, jeśli nie ma go w obiekcie książki
const {
title,
coverImage = "https://via.placeholder.com/640/480",
author,
} = book;
console.log(title);// The Last Kingdom
console.log(author);// Bernard Cornwell
console.log(coverImage);// https://via.placeholder.com/640/480
Section2 Article4: Zmiana nazwy zmiennej
Podczas destrukturyzacji możesz zmienić nazwę zmiennej, do której rozpakowana jest wartość właściwości. Aby to osiągnąć najpierw piszemy nazwę właściwości, z której chcemy uzyskać wartość, po czym wstawiamy dwukropek i wpisujemy nazwę zmiennej, w której ma być umieszczona wartość tej właściwości.
const firstBook = {
title: "The Last Kingdom",
coverImage:
"https://images-na.ssl-images-amazon.com/images/I/51b5YG6Y1rL.jpg",
};
const {
title: firstTitle,
coverImage: firstCoverImage = "https://via.placeholder.com/640/480",
} = firstBook;
console.log(firstTitle);// The Last Kingdom
console.log(firstCoverImage);// https://images-na.ssl-images-amazon.com/images/I/51b5YG6Y1rL.jpg
const secondBook = {
title: "Sen śmiesznego człowieka",
};
const {
title: secondTitle,
coverImage: secondCoverImage = "https://via.placeholder.com/640/480",
} = secondBook;
console.log(secondTitle);// Sen śmiesznego człowieka
console.log(secondCoverImage);// https://via.placeholder.com/640/480
Tego typu zapis możemy zrozumieć jako: "Utwórz zmienną firstTitle, w której należy umieścić wartość właściwości title z obiektu firstBook" itd.
Section2 Article5: Destrukturyzacja w pętlach
Podczas iteracji po tablicy obiektów z pętlą for...of często będzie wielokrotnie wywoływać obiekt.
const books = [
{
title: "The Last Kingdom",
author: "Bernard Cornwell",
rating: 8.38,
},
{
title: "Beside Still Waters",
author: "Robert Sheckley",
rating: 8.51,
},
];
for (const book of books) {
console.log(book.title);
console.log(book.author);
console.log(book.rating);
}
Aby zmniejszyć liczbę powtórzeń w kodzie, możesz zdestrukturyzować właściwości obiektu na zmienne lokalne w deklaracji pętli.
const books = [
{
title: "The Last Kingdom",
author: "Bernard Cornwell",
rating: 8.38,
},
{
title: "Beside Still Waters",
author: "Robert Sheckley",
rating: 8.51,
},
];
for (const book of books) {
const { title, author, rating } = book;
console.log(title);
console.log(author);
console.log(rating);
}
Jeśli obiekt ma niewiele właściwości, destrukturyzację można przeprowadzić bezpośrednio w miejscu, w którym deklarowana jest zmienna book
const books = [
{
title: "The Last Kingdom",
author: "Bernard Cornwell",
rating: 8.38,
},
{
title: "Beside Still Waters",
author: "Robert Sheckley",
rating: 8.51,
},
];
for (const { title, author, rating } of books) {
console.log(title);
console.log(author);
console.log(rating);
}
Section2 Article6: Głęboka destrukturyzacja
Do destrukturyzacji właściwości obiektów zagnieżdżonych są używane te same zasady, jak w poprzednich trzech ćwiczeniach.
const user = {
name: "Jacques Gluke",
tag: "jgluke",
stats: {
followers: 5603,
views: 4827,
likes: 1308,
},
};
const {
name,
tag,
stats: { followers, views: userViews, likes: userLikes = 0 },
} = user;
console.log(name); // Jacques Gluke
console.log(tag); // jgluke
console.log(followers); // 5603
console.log(userViews); // 4827
console.log(userLikes); // 1308
Section3 Article1: Destrukturyzacja tablic
Przypisanie destrukturyzujące może być również użyte dla tablic, ale będzie się nieco różniło:
- Użyj nawiasów kwadratowych [] zamiast nawiasów klamrowych {}
- Zmiennym określonym w nawiasach kwadratowych [], będą po kolei przypisywane wartości elementów tablicy - kolejność zmiennych będzie więc miała znaczenie.
Dla przykładu weźmy tablicę kolorów, z której należy pobrać wartości każdego składnika koloru do osobnych zmiennych.
const rgb = [200, 255, 100];
const [red, green, blue] = rgb;
console.log(`R:${red},G:${green},B:${blue}`);// "R:200,G:255,B:100"
Po słowie kluczowym const lub let umieszczamy otwierający i zamykający nawias kwadratowy, tak jak przy deklarowaniu tablicy. Wewnątrz nawiasów, oddzielone przecinkami, wskazujemy nazwy zmiennych, w których zostaną umieszczone wartości tablicy.
W wyniku zapisu powyżej zostaną utworzone 3 zmienne, a elementy zostaną w nich umieszczone w kolejności numeracji - od 0 do końca tablicy.
Podczas destrukturyzacji tablic wartość zmiennej można przypisać już po jej deklaracji. W praktyce jest to rzadko używane i zmniejsza czytelność.
const rgb = [200, 255, 100];
let red, green, blue;
[red, green, blue] = rgb;
console.log(`R:${red},G:${green},B:${blue}`);// "R:200,G:255,B:100"
Jeśli podamy więcej zmiennych niż elementów tablicy, zostaną im przypisane wartości undefined, możemy więc również skorzystać z zapisu wartości domyślnych.
const rgb = [200, 100, 255];
const [red, green, blue, alfa = 0.3] = rgb;
console.log(`R:${red},G:${green},B:${blue},Alfa:${alfa}`);// "R:200,G:100,B:255,Alfa:0.3"
Czasami konieczna jest destrukturyzacja tylko pierwszych N elementów z tablicy i przechowywanie pozostałych w jednej zmiennej w postaci tablicy. Podczas destrukcji tablicy możesz rozpakować i przypisać pozostałe elementy tablicy do zmiennej za pomocą operacji ... (rest).
const rgb = [200, 255, 100];
const [red, ...colors] = rgb;
console.log(red);// "200"
console.log(colors);// [255, 100]
Elementy możemy też pominąć. Załóżmy, że musisz pobrać tylko ostatnią wartość z tablicy rgb. Taki zapis nie jest zbyt często używany, ale poznajmy go:
const rgb = [200, 100, 255];
const [, , blue] = rgb;
console.log(`Blue: ${blue}`);// "Blue: 255"
Section4 Article1: Wzorzec "Obiekt parametru"
Jeśli funkcja przyjmuje więcej niż dwa lub trzy argumenty, bardzo łatwo jest pomylić się, w jakiej kolejności należy przekazać wartości. Rezultatem jest bardzo nieczytelny kod w miejscu jego wywołania i trudne do odnalezienia na pierwszy rzut oka błędy.
function doStuffWithBook(title, numberOfPages, downloads, rating, public) {
// wykorzystanie parametrów
console.log(title);
console.log(numberOfPages);
// dalsza część kodu
}
// ❌ Co to jest 736? Co to jest 10283? Co to jest true?
doStuffWithBook("The Last Kingdom", 736, 10283, 8.38, true);
Wzorzec "Obiekt parametru" pomaga rozwiązać ten problem, zastępując zestaw parametrów tylko jednym — obiektem o nazwanych właściwościach
function doStuffWithBook(book) {
// wykorzystanie parametru (obiektu)
console.log(book.title);
console.log(book.numberOfPages);
// dalsza część kodu
}
Następnie podczas wywołania przekazujemy jeden obiekt z niezbędnymi właściwościami.
// ✅ wykorzystanie wszystkich zmiennych jest jasne
doStuffWithBook({
title: "The Last Kingdom",
numberOfPages: 736,
downloads: 10283,
rating: 8.38,
isPublic: true,
});
Kolejnym plusem jest to, że możesz również zdestrukturyzować obiekt w parametrze book. Można to zrobić na dwa sposoby, albo w ciele funkcji.
function doStuffWithBook(book) {
const { title, numberOfPages, downloads, rating, isPublic } = book;
console.log(title);
console.log(numberOfPages);
// dalsza część kodu
}
Lub od razu w sygnaturze funkcji, oba zapisy są poprawne
function doStuffWithBook({
title,
numberOfPages,
downloads,
rating,
isPublic,
}) {
console.log(title);
console.log(numberOfPages);
// dalsza część kodu
}