Destrukční úkol

Dvě nejpoužívanější datové struktury v JavaScriptu jsou Object a Array .

  • Objekty nám umožňují vytvořit jedinou entitu, která ukládá datové položky podle klíče.
  • Pole nám umožňují shromažďovat datové položky do uspořádaného seznamu.

I když, když je předáme funkci, nemusí to být objekt/pole jako celek. Může to vyžadovat jednotlivé kusy.

Destrukční přiřazení je speciální syntaxe, která nám umožňuje „rozbalit“ pole nebo objekty do hromady proměnných, jak je to někdy pohodlnější.

Destructuring funguje skvěle i u složitých funkcí, které mají spoustu parametrů, výchozích hodnot a tak dále. Brzy to uvidíme.

Destrukce pole

Zde je příklad toho, jak je pole destrukturováno na proměnné:

// we have an array with the name and surname
let arr = ["John", "Smith"]

// destructuring assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname); // Smith

Nyní můžeme pracovat s proměnnými namísto členů pole.

Vypadá skvěle v kombinaci s split nebo jiné metody vracející pole:

let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname); // Smith

Jak vidíte, syntaxe je jednoduchá. Existuje však několik zvláštních detailů. Podívejme se na další příklady, abychom tomu lépe porozuměli.

„Zničení“ neznamená „destruktivní“.

Říká se tomu „destrukční přiřazení“, protože „destrukturuje“ kopírováním položek do proměnných. Ale samotné pole není změněno.

Je to jen kratší způsob psaní:

// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
Ignorujte prvky pomocí čárek

Nežádoucí prvky pole lze také zahodit pomocí čárky navíc:

// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

Ve výše uvedeném kódu je druhý prvek pole přeskočen, třetí je přiřazen k title a zbytek položek pole je také přeskočen (protože pro ně nejsou žádné proměnné).

Funguje s libovolnou iterovatelnou na pravé straně

…Ve skutečnosti to můžeme použít s libovolnými iterovatelnými, nejen s poli:

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);

To funguje, protože interně destrukční přiřazení funguje tak, že se iteruje přes správnou hodnotu. Je to druh syntaxe pro volání for..of přes hodnotu napravo od = a přiřazení hodnot.

Přiřadit k čemukoli na levé straně

Můžeme použít jakékoli „přiřaditelné“ na levé straně.

Například vlastnost objektu:

let user = {};
[user.name, user.surname] = "John Smith".split(' ');

alert(user.name); // John
alert(user.surname); // Smith
Opakování pomocí .entries()

V předchozí kapitole jsme viděli metodu Object.entries(obj).

Můžeme jej použít s destrukcí k opakování klíčů a hodnot objektu:

let user = {
 name: "John",
 age: 30
};

// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
 alert(`${key}:${value}`); // name:John, then age:30
}

Podobný kód pro Map je jednodušší, protože je iterovatelný:

let user = new Map();
user.set("name", "John");
user.set("age", "30");

// Map iterates as [key, value] pairs, very convenient for destructuring
for (let [key, value] of user) {
 alert(`${key}:${value}`); // name:John, then age:30
}
Trik s výměnou proměnných

Existuje známý trik pro záměnu hodnot dvou proměnných pomocí destrukčního přiřazení:

let guest = "Jane";
let admin = "Pete";

// Let's swap the values: make guest=Pete, admin=Jane
[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)

Zde vytvoříme dočasné pole dvou proměnných a okamžitě je destrukujeme ve swapovaném pořadí.

Tímto způsobem můžeme zaměnit více než dvě proměnné.

Zbytek „…“

Obvykle, pokud je pole delší než seznam vlevo, položky „extra“ jsou vynechány.

Například zde se berou pouze dvě položky a zbytek je ignorován:

let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar
// Further items aren't assigned anywhere

Pokud bychom také chtěli shromáždit vše, co následuje – můžeme přidat ještě jeden parametr, který dostane „zbytek“ pomocí tří teček "..." :

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

// rest is array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

Hodnota rest je pole zbývajících prvků pole.

Místo rest můžeme použít jakýkoli jiný název proměnné , jen se ujistěte, že má před sebou tři tečky a je poslední v destrukčním úkolu.

let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// now titles = ["Consul", "of the Roman Republic"]

Výchozí hodnoty

Pokud je pole kratší než seznam proměnných vlevo, nebudou se vyskytovat žádné chyby. Nepřítomné hodnoty jsou považovány za nedefinované:

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

Pokud chceme, aby „výchozí“ hodnota nahradila chybějící, můžeme ji poskytnout pomocí = :

// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name); // Julius (from array)
alert(surname); // Anonymous (default used)

Výchozí hodnoty mohou být složitější výrazy nebo dokonce volání funkcí. Vyhodnocují se pouze v případě, že není zadána hodnota.

Například zde používáme prompt funkce pro dvě výchozí hodnoty:

// runs only prompt for surname
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];

alert(name); // Julius (from array)
alert(surname); // whatever prompt gets

Poznámka:prompt poběží pouze pro chybějící hodnotu (surname ).

Zničení objektu

Destrukční přiřazení pracuje také s objekty.

Základní syntaxe je:

let {var1, var2} = {var1:…, var2:…}

Na pravé straně bychom měli mít existující objekt, který chceme rozdělit na proměnné. Levá strana obsahuje objektový „vzor“ pro odpovídající vlastnosti. V nejjednodušším případě je to seznam názvů proměnných v {...} .

Například:

let options = {
 title: "Menu",
 width: 100,
 height: 200
};

let {title, width, height} = options;

alert(title); // Menu
alert(width); // 100
alert(height); // 200

Vlastnosti options.title , options.width a options.height jsou přiřazeny odpovídajícím proměnným.

Na pořadí nezáleží. Toto funguje také:

// changed the order in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }

Vzor na levé straně může být složitější a může specifikovat mapování mezi vlastnostmi a proměnnými.

Pokud například chceme přiřadit vlastnost proměnné s jiným názvem, udělejte options.width přejděte do proměnné s názvem w , pak můžeme nastavit název proměnné pomocí dvojtečky:

let options = {
 title: "Menu",
 width: 100,
 height: 200
};

// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title); // Menu
alert(w); // 100
alert(h); // 200

Dvojtečka ukazuje „co:jde kam“. V příkladu výše vlastnost width přejde na w , vlastnost height přejde na h a title je přiřazeno ke stejnému názvu.

Pro potenciálně chybějící vlastnosti můžeme nastavit výchozí hodnoty pomocí "=" , takto:

let options = {
 title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title); // Menu
alert(width); // 100
alert(height); // 200

Stejně jako u polí nebo parametrů funkcí mohou být výchozí hodnoty libovolné výrazy nebo dokonce volání funkcí. Pokud hodnotu nezadáte, budou vyhodnoceny.

V níže uvedeném kódu prompt požádá o width , ale ne pro title :

let options = {
 title: "Menu"
};

let {width = prompt("width?"), title = prompt("title?")} = options;

alert(title); // Menu
alert(width); // (whatever the result of prompt is)

Můžeme také kombinovat dvojtečku a rovnost:

let options = {
 title: "Menu"
};

let {width: w = 100, height: h = 200, title} = options;

alert(title); // Menu
alert(w); // 100
alert(h); // 200

Pokud máme komplexní objekt s mnoha vlastnostmi, můžeme extrahovat pouze to, co potřebujeme:

let options = {
 title: "Menu",
 width: 100,
 height: 200
};

// only extract title as a variable
let { title } = options;

alert(title); // Menu

Zbývající vzor „…“

Co když má objekt více vlastností, než máme proměnných? Můžeme si nějaké vzít a pak „zbytek“ někam přiřadit?

Můžeme použít zbytek vzoru, stejně jako jsme to udělali s poli. Některé starší prohlížeče jej nepodporují (IE, použijte Babel k jeho polyfill), ale funguje v moderních.

Vypadá to takto:

let options = {
 title: "Menu",
 height: 200,
 width: 100
};

// title = property named title
// rest = object with the rest of properties
let {title, ...rest} = options;

// now title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
Mám problém, pokud neexistuje let

Ve výše uvedených příkladech byly proměnné deklarovány přímo v přiřazení:let {…} = {…} . Samozřejmě bychom mohli použít i existující proměnné bez let . Má to ale háček.

Toto nebude fungovat:

let title, width, height;

// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};

Problém je v tom, že JavaScript zpracovává {...} v hlavním toku kódu (nikoli uvnitř jiného výrazu) jako blok kódu. Takové bloky kódu lze použít ke seskupování příkazů, jako je tento:

{
 // a code block
 let message = "Hello";
 // ...
 alert( message );
}

Zde tedy JavaScript předpokládá, že máme blok kódu, proto došlo k chybě. Místo toho chceme destrukturaci.

Abychom JavaScriptu ukázali, že se nejedná o blok kódu, můžeme výraz zabalit do závorek (...) :

let title, width, height;

// okay now
({title, width, height} = {title: "Menu", width: 200, height: 100});

alert( title ); // Menu

Vnořené zničení

Pokud objekt nebo pole obsahuje další vnořené objekty a pole, můžeme k extrahování hlubších částí použít složitější vzory na levé straně.

V níže uvedeném kódu options má jiný objekt ve vlastnosti size a pole ve vlastnosti items . Vzor na levé straně zadání má stejnou strukturu pro extrakci hodnot z nich:

let options = {
 size: {
 width: 100,
 height: 200
 },
 items: ["Cake", "Donut"],
 extra: true
};

// destructuring assignment split in multiple lines for clarity
let {
 size: { // put size here
 width,
 height
 },
 items: [item1, item2], // assign items here
 title = "Menu" // not present in the object (default value is used)
} = options;

alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut

Všechny vlastnosti options objekt kromě extra které v levé části chybí, jsou přiřazeny odpovídajícím proměnným:

Nakonec máme width , height , item1 , item2 a title z výchozí hodnoty.

Všimněte si, že pro size nejsou žádné proměnné a items , protože místo toho bereme jejich obsah.

Parametry inteligentních funkcí

Jsou chvíle, kdy má funkce mnoho parametrů, z nichž většina je volitelná. To platí zejména pro uživatelská rozhraní. Představte si funkci, která vytváří menu. Může mít šířku, výšku, nadpis, seznam položek a tak dále.

Zde je špatný způsob, jak takovou funkci napsat:

function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
 // ...
}

V reálném životě je problém, jak si zapamatovat pořadí argumentů. Obvykle se nám IDE snaží pomoci, zvláště pokud je kód dobře zdokumentován, ale přesto... Dalším problémem je, jak zavolat funkci, když je většina parametrů ve výchozím nastavení v pořádku.

Takhle?

// undefined where default values are fine
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])

to je ošklivé. A stává se nečitelným, když se zabýváme více parametry.

Destrukce přichází na pomoc!

Parametry můžeme předat jako objekt a funkce je okamžitě zničí na proměnné:

// we pass object to function
let options = {
 title: "My menu",
 items: ["Item1", "Item2"]
};

// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
 // title, items – taken from options,
 // width, height – defaults used
 alert( `${title} ${width} ${height}` ); // My Menu 200 100
 alert( items ); // Item1, Item2
}

showMenu(options);

Můžeme také použít složitější destrukturování s vnořenými objekty a dvojtečkami:

let options = {
 title: "My menu",
 items: ["Item1", "Item2"]
};

function showMenu({
 title = "Untitled",
 width: w = 100, // width goes to w
 height: h = 200, // height goes to h
 items: [item1, item2] // items first element goes to item1, second to item2
}) {
 alert( `${title} ${w} ${h}` ); // My Menu 100 200
 alert( item1 ); // Item1
 alert( item2 ); // Item2
}

showMenu(options);

Úplná syntaxe je stejná jako u destrukčního přiřazení:

function({
 incomingProperty: varName = defaultValue
 ...
})

Pak pro objekt parametrů bude proměnná varName pro vlastnost incomingProperty , s defaultValue ve výchozím nastavení.

Vezměte prosím na vědomí, že takové zničení předpokládá, že showMenu() má argument. Pokud chceme všechny hodnoty jako výchozí, pak bychom měli zadat prázdný objekt:

showMenu({}); // ok, all values are default

showMenu(); // this would give an error

Můžeme to opravit vytvořením {} výchozí hodnota pro celý objekt parametrů:

function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
 alert( `${title} ${width} ${height}` );
}

showMenu(); // Menu 100 200

Ve výše uvedeném kódu je celý objekt arguments {} ve výchozím nastavení, takže je vždy co destrukovat.

Shrnutí

  • Destrukční přiřazení umožňuje okamžité mapování objektu nebo pole na mnoho proměnných.

  • Úplná syntaxe objektu:

    let {prop : varName = default, ...rest} = object

    To znamená, že vlastnost prop by měl jít do proměnné varName a pokud žádná taková vlastnost neexistuje, pak default hodnota by měla být použita.

    Vlastnosti objektu, které nemají žádné mapování, se zkopírují do rest objekt.

  • Úplná syntaxe pole:

    let [item1 = default, item2, ...rest] = array

    První položka přejde na item1; druhý přejde do item2 , zbytek tvoří pole rest .

  • Je možné extrahovat data z vnořených polí/objektů, proto levá strana musí mít stejnou strukturu jako pravá.