Destrukturyzacja

> Dodaj do ulubionych

ES6 bez tajemnic to cykl artyku┼é├│w po┼Ťwi─Öconych nowym sk┼éadnikom j─Özyka JavaScript, kt├│re pojawi┼éy si─Ö w 6. edycji standardu ECMAScript, w skr├│cie ES6.

Uwaga od redaktora: pierwotna wersja niniejszego artykułu autorstwa inżyniera ds. narzędzi programistycznych w Firefoksie Nicka Fitzgeralda ukazała się na jego blogu pod tytułem Destructuring Assignment in ES6.

Czym jest przypisanie destrukturyzuj─ůce?

Przypisanie destrukturyzuj─ůce umo┼╝liwia przypisanie w┼éasno┼Ťci tablicy lub obiektu do zmiennych z wykorzystaniem sk┼éadni przypominaj─ůcej sk┼éadni─Ö tablic czy litera┼é├│w obiektowych. Mo┼╝e by─ç ona niezwykle zwi─Öz┼éa, a jednocze┼Ťnie znacznie czytelniejsza od tradycyjnego kodu s┼éu┼╝─ůcego do uzyskania dost─Öpu do w┼éasno┼Ťci.

Nie korzystaj─ůc z przypisania destrukturyzuj─ůcego, dost─Öp do pierwszych trzech element├│w tablicy mo┼╝emy uzyska─ç w nast─Öpuj─ůcy spos├│b:

var first = jaka┼ŤTamTablica[0];
var second = jaka┼ŤTamTablica[1];
var third = jaka┼ŤTamTablica[2];

Je┼Ťli natomiast zastosujemy przypisanie destrukturyzuj─ůce, ten sam kod mo┼╝na wyrazi─ç w bardziej zwi─Öz┼éy i czytelny spos├│b:


var [first, second, third] = jaka┼ŤTamTablica;

SpiderMonkey (silnik JavaScriptu w Firefoksie) obs┼éuguje ju┼╝ destrukturyzacj─Ö w znacznym stopniu, jednak wci─ů┼╝ wymagane s─ů w tym zakresie poprawki. Tutaj mo┼╝esz ┼Ťledzi─ç b┼é─ůd 694100 dotycz─ůcy obs┼éugi destrukturyzacji (i ca┼éego ES6) w silniku SpiderMonkey.

Destrukturyzacja tablic i obiekt├│w iterowalnych

Widzieli┼Ťmy ju┼╝ przyk┼éad wykorzystania przypisania destrukturyzuj─ůcego w przedstawionej powy┼╝ej tablicy. Standardowa sk┼éadnia przypisania destrukturyzuj─ůcego wygl─ůda nast─Öpuj─ůco:


[ zmienna1, zmienna2, ..., zmiennaN ] = nazwaTablicy;

Kod ten przypisze po prostu wszystkie zmienne pocz─ůwszy od zmiennej1 do zmiennej N do odpowiadaj─ůcego im elementu w danej tablicy. Je┼Ťli w tym samym kodzie chcia┼éby┼Ť r├│wnie┼╝ zadeklarowa─ç zmienne, mo┼╝esz to zrobi─ç, dodaj─ůc s┼éowa kluczowe var, let, b─ůd┼║ const na pocz─ůtku przypisania:


var [ zmienna1, zmienna2, ..., zmiennaN ] = nazwaTablicy;
let [ zmienna1, zmienna2, ..., zmiennaN ] = nazwaTablicy ] = nazwaTablicy;
const [ zmienna1, zmienna2, ..., zmiennaN ] = nazwaTablicy ] = nazwaTablicy;

Nazwa ÔÇ×zmiennaÔÇŁ jest w gruncie rzeczy myl─ůca, poniewa┼╝ mo┼╝emy zagnie┼║dzi─ç parametry w kodzie tak g┼é─Öboko, jak tylko chcemy:


var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3

Ponadto mo┼╝liwe jest pomini─Öcie element├│w poddawanej destrukturyzacji tablicy:


var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"

Mo┼╝na tak┼╝e uj─ů─ç wszystkie elementy tablicy za pomoc─ů parametru resztowego:


var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]

Podczas pr├│by uzyskania dost─Öpu do element├│w nieistniej─ůcych b─ůd┼║ znajduj─ůcych si─Ö poza zakresem tablicy, zwracana jest ta sama warto┼Ť─ç co w przypadku indeksowania, czyli undefined.


console.log([][0]);
// undefined

var [missing] = [];
console.log(missing);
// undefined

Warto zauwa┼╝y─ç, ┼╝e przypisanie destrukturyzuj─ůce wykorzystuj─ůce wzorzec przypisania tablicy dzia┼éa tak┼╝e w przypadku dowolnego obiektu iterowalnego:


function* fibs() {
  var a = 0;
  var b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5

Destrukturyzacja obiekt├│w

Destrukturyzacja obiekt├│w pozwala powi─ůza─ç zmienne z innymi w┼éasno┼Ťciami danego obiektu. Nale┼╝y najpierw okre┼Ťli─ç w┼éasno┼Ť─ç, a nast─Öpnie poda─ç zmienn─ů, kt├│rej warto┼Ť─ç ma zosta─ç powi─ůzana z wybran─ů w┼éasno┼Ťci─ů.


var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };

var { name: nameA } = robotA;
var { name: nameB } = robotB;

console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"

Istnieje przydatny skr├│t sk┼éadniowy, kt├│ry mo┼╝na wykorzysta─ç, je┼Ťli wybrana w┼éasno┼Ť─ç i zmienna maj─ů identyczne nazwy:


var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// lorem
console.log(bar);
// ipsum

Podobnie jak w przypadku destrukturyzacji tablic, tak i w przypadku obiekt├│w mo┼╝emy zagnie┼╝d┼╝a─ç i ┼é─ůczy─ç ze sob─ů dalsze przypisania destrukturyzuj─ůce:


var complicatedObj = {
  arrayProp: [
    "Zapp",
    { second: "Brannigan" }
  ]
};

var { arrayProp: [first, { second }] } = complicatedObj;

console.log(first);
// Zapp
console.log(second);
// Brannigan

W przypadku pr├│by destrukturyzacji niezdefiniowanych w┼éasno┼Ťci, zwracana jest warto┼Ť─ç undefined:


var { missing } = {};
console.log(missing);
// undefined

Warto pami─Öta─ç, ┼╝e je┼Ťli skorzystamy z destrukturyzacji obiektu jedynie w celu przypisania zmiennych, bez ich deklaracji (czyli je┼Ťli w naszym kodzie nie b─Ödzie s┼éowa kluczowego let, const, b─ůd┼║ var), czyha─ç b─Ödzie na nas pu┼éapka:

{ blowUp } = { blowUp: 10 };
// b┼é─ůd sk┼éadni

Dzieje si─Ö tak, poniewa┼╝ gramatyka JavaScriptu wymusza traktowanie dowolnej instrukcji rozpoczynaj─ůcej si─Ö od znaku { jako instrukcji blokowej (przyk┼éadowo { console } to poprawna instrukcja blokowa). Mo┼╝na ten problem rozwi─ůza─ç, umieszczaj─ůc ca┼ée wyra┼╝enie w nawiasie okr─ůg┼éym:

({ safe } = {});
// brak błędów

Destrukturyzacja warto┼Ťci nieb─Öd─ůcych obiektem zwyk┼éym, obiektem iterowalnym ani tablic─ů

Pr├│ba destrukturyzacji warto┼Ťci null lub undefined spowoduje b┼é─ůd typu:

var {blowUp} = null;
// TypeError: null has no properties

Mo┼╝liwa jest jednak destrukturyzacja typ├│w prostych, takich jak typy logiczne, liczby i ┼éa┼äcuchy, ale w├│wczas zwracana jest warto┼Ť─ç undefined:

var {wtf} = NaN;
console.log(wtf);
// undefined

Mo┼╝e by─ç to zaskakuj─ůce, jednak je┼Ťli si─Ö nad tym zastanowimy, to przyczyna jest prosta. Podczas korzystania z wzorca przypisania obiektu destrukturyzowana warto┼Ť─ç musi by─ç typu, kt├│ry da si─Ö rzutowa─ç do postaci obiektu. Wi─Ökszo┼Ť─ç typ├│w mo┼╝na rzutowa─ç do obiektu, lecz nie warto┼Ťci null i undefined. Je┼Ťli natomiast korzystamy z wzorca przypisania tablicy, warto┼Ť─ç musi mie─ç iterator.

Domy┼Ťlne warto┼Ťci

Mo┼╝na r├│wnie┼╝ okre┼Ťli─ç warto┼Ťci domy┼Ťlne, na wypadek gdyby poddawana destrukturyzacji w┼éasno┼Ť─ç by┼éa niezdefiniowana:

var [missing = true] = [];
console.log(missing);
// true

var { message: msg = "Co┼Ť posz┼éo nie tak." } = {};
console.log(msg);
//  "Co┼Ť posz┼éo nie tak." 

var { x = 3 } = {};
console.log(x);
// 3

(Uwaga od redaktora: aktualnie z tego składnika można korzystać w Firefoksie tylko w pierwszych dwóch omawianych przypadkach. Szczegółowe informacje znajdziesz w opisie błędu 932080).

Praktyczne zastosowania destrukturyzacji

Definiowanie parametr├│w funkcji

My, programi┼Ťci, cz─Östo wolimy udost─Öpnia─ç bardziej ergonomiczne interfejsy API akceptuj─ůce parametry b─Öd─ůce pojedynczymi obiektami z wieloma w┼éasno┼Ťciami ni┼╝ zmusza─ç korzystaj─ůcych z naszego API do zapami─Ötania kolejno┼Ťci wielu pojedynczych parametr├│w. Destrukturyzacja pomo┼╝e nam unikn─ů─ç powtarzania jednoparametrowego obiektu za ka┼╝dym razem, gdy chcemy odwo┼éa─ç si─Ö do jednej z jego w┼éasno┼Ťci:

function removeBreakpoint({ url, line, column }) {
  // ...
}

To uproszczony fragment prawdziwego kodu z debuggera JavaScript dost─Öpnego w narz─Ödziach dla tw├│rc├│w witryn przegl─ůdarki Firefox (swoj─ů drog─ů, i on zaimplementowany jest w JavaScripcie). Naszym zdaniem to bardzo przyjemny wzorzec.

Parametry obiektu konfiguracji

Kontynuuj─ůc poprzedni przyk┼éad, istnieje r├│wnie┼╝ mo┼╝liwo┼Ť─ç przypisania warto┼Ťci domy┼Ťlnych do w┼éasno┼Ťci destrukturyzowanych obiekt├│w. Jest to szczeg├│lnie pomocne w sytuacji, w kt├│rej obiekt ma zawiera─ç dane konfiguracyjne, a wiele z w┼éasno┼Ťci obiekt├│w ma ju┼╝ okre┼Ťlone dobre warto┼Ťci domy┼Ťlne. Przyk┼éadowo dost─Öpna w jQuery funkcja ajax przyjmuje obiekt konfiguracji jako drugi parametr i mo┼╝na j─ů przepisa─ç tak:

jQuery.ajax = function (url, {
  async = true,
  beforeSend = noop,
  cache = true,
  complete = noop,
  crossDomain = false,
  global = true,
  // ... wi─Öcej danych konfiguracyjnych
}) {
  // ... zr├│b co┼Ť
};

W ten spos├│b unikamy powtarzania var foo = config.foo || theDefaultFoo; dla ka┼╝dej w┼éasno┼Ťci obiektu konfiguracji.

(Uwaga od redaktora: niestety, w Firefoksie wci─ů┼╝ nie mo┼╝na u┼╝ywa─ç warto┼Ťci domy┼Ťlnych w skr├│conej sk┼éadni obiekt├│w. Wiem, wiem, po┼Ťwi─Öcili┼Ťmy im kilka akapit├│w mimo poprzedniej adnotacji. Najnowsze informacje na ten temat dost─Öpne s─ů w opisie b┼é─Ödu 932080).

Destrukturyzacja a protokół iteracji w ES6

W standardzie ECMAScript 6 zdefiniowany jest r├│wnie┼╝ protok├│┼é iteracji, o kt├│rym m├│wili┼Ťmy w jednym z poprzednich artyku┼é├│w. Kiedy przegl─ůdamy iteracyjnie obiekt Map (pojawiaj─ůcy si─Ö w ES6 dodatek do biblioteki standardowej), otrzymujemy seri─Ö par [klucz, warto┼Ť─ç]. Tak─ů par─Ö mo┼╝na podda─ç destrukturyzacji i w ten spos├│b uzyska─ç dost─Öp zar├│wno do klucza, jak i do warto┼Ťci:

var map = new Map();
map.set(window, "obiekt globalny");
map.set(document, "dokument");

for (var [key, value] of map) {
  console.log(key + " to " + value);
}
// "[object Window] to obiekt globalny"
// "[object HTMLDocument] to dokument"

Kod przegl─ůdaj─ůcy iteracyjnie tylko klucze:

for (var [key] of map) {
  // ...
}

lub tylko warto┼Ťci:

for (var [,value] of map) {
  // ...
}

Wiele zwracanych warto┼Ťci

Cho─ç zwracanie wielu warto┼Ťci nie jest standardowym sk┼éadnikiem funkcjonalno┼Ťci JavaScriptu, to mo┼╝na tego dokona─ç poprzez zwr├│cenie tablicy i poddanie jej destrukturyzacji:

function returnMultipleValues() {
  return [1, 2];
}
var [foo, bar] = returnMultipleValues();

Mo┼╝na tak┼╝e wykorzysta─ç obiekt jako kontener i nazwa─ç zwracane warto┼Ťci:

function returnMultipleValues() {
  return {
    foo: 1,
    bar: 2
  };
}
var { foo, bar } = returnMultipleValues();

Oba te rozwi─ůzania sprawdzaj─ů si─Ö o wiele lepiej ni┼╝ kontener tymczasowy:

function returnMultipleValues() {
  return {
    foo: 1,
    bar: 2
  };
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;

S─ů te┼╝ lepsze od stosowania stylu przekazywania kontynuacji (ang. continuation-passing style):

function returnMultipleValues(k) {
  k(1, 2);
}
returnMultipleValues((foo, bar) => ...);

Importowanie nazw z modułów CommonJS

Nie korzystasz jeszcze z modu┼é├│w ES6? Wci─ů┼╝ polegasz na modu┼éach CommonJS? ┼╗aden problem! Podczas importowania modu┼é├│w CommonJS cz─Östo zdarza si─Ö, ┼╝e dany modu┼é eksportuje wi─Öcej funkcji ni┼╝ potrzeba. Dzi─Öki destrukturyzacji mo┼╝emy okre┼Ťli─ç, z kt├│rych cz─Ö┼Ťci modu┼éu chcemy skorzysta─ç, tym samym nie zajmuj─ůc niepotrzebnie naszej przestrzeni nazw:

const { SourceMapConsumer, SourceNode } = require("source-map");

(Je┼Ťli jednak korzystasz z modu┼é├│w ES6 to wiesz, ┼╝e podobna sk┼éadnia jest dost─Öpna w deklaracjach import).

Podsumowanie

Jak widzisz, destrukturyzacja przydaje si─Ö do rozwi─ůzywania wielu pojedynczych problem├│w. W Mozilli mamy ju┼╝ w niej spore do┼Ťwiadczenie. Dziesi─Ö─ç lat temu Lars Hansen zaimplementowa┼é destrukturyzacj─Ö JS w Operze, a nieco p├│┼║niej obs┼éug─Ö destrukturyzacji doda┼é do Firefoksa Brendan Eich. Oficjalnie pojawi┼éa si─Ö ona w Firefoksie 2. Wiemy ju┼╝, ┼╝e destrukturyzacja towarzyszy programistom JavaScriptu na co dzie┼ä, dyskretnie pomagaj─ůc pisa─ç nieco kr├│tszy i schludniejszy kod.

Powiedzieli┼Ťmy kiedy┼Ť, ┼╝e ES6 zmieni spos├│b, w jaki piszemy kod JavaScript. Mieli┼Ťmy w├│wczas na my┼Ťli szczeg├│lnie tego rodzaju elementy: proste usprawnienia, kt├│rych mo┼╝na nauczy─ç si─Ö z osobna. Wszystkie razem w ko┼äcu sprawi─ů, ┼╝e do pracy nad ka┼╝dym projektem b─Ödziesz podchodzi─ç inaczej. Rewolucja poprzez ewolucj─Ö.

Nad zaktualizowaniem destrukturyzacji pod k─ůtem ES6 pracowa┼é ca┼éy zesp├│┼é ludzi. Szczeg├│lne podzi─Ökowania nale┼╝─ů si─Ö Tooru Fujisawie (arai) i Arpadowi Borsosowi (Swatinem) za ich nieoceniony wk┼éad.

Obecnie trwaj─ů prace nad zaimplementowaniem destrukturyzacji w przegl─ůdarce Chrome. Obs┼éuga tej techniki z pewno┼Ťci─ů zostanie r├│wnie┼╝ niebawem dodana do innych przegl─ůdarek. Na t─Ö chwil─Ö, by u┼╝ywa─ç ┼éa┼äcuch├│w szablonowych w kodzie przegl─ůdarkowym trzeba skorzysta─ç z kompilatora Babel lub Traceur.

Raz jeszcze dziękuję Nickowi Fitzgeraldowi za niniejszy artykuł.

W kolejnej ods┼éonie serii om├│wimy sk┼éadnik ES6, za po┼Ťrednictwem kt├│rego b─Ödziemy mogli ÔÇô ni mniej, ni wi─Öcej ÔÇô pisa─ç skr├│cony kod czego┼Ť, co jest ju┼╝ dost─Öpne w JS. Czego┼Ť, co od zawsze stanowi┼éo jeden z kluczowych element├│w tego j─Özyka. Czy mog┼éoby ci─Ö to w og├│le zainteresowa─ç? Czy nieco kr├│tsza sk┼éadnia to co┼Ť, czego nie mo┼╝esz si─Ö doczeka─ç? M├│g┼ébym si─Ö za┼éo┼╝y─ç, ┼╝e odpowied┼║ brzmi ÔÇ×takÔÇŁ, ale niczego nie obiecuj─Ö. Odwiedzaj nas zatem cz─Östo, by przekona─ç si─Ö samemu i szczeg├│┼éowo pozna─ç funkcje strza┼ékowe w ES6.

Jason Orendorff ÔÇö redaktor serii ES6 bez tajemnic

Autor: Nick Fitzgerald i Jason Orendorff

Źródło: https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/

Tłumaczenie: Joanna Liana

Tre┼Ť─ç tej strony jest dost─Öpna na zasadach licencji CC BY-SA 3.0