Tajniki jezyka JavaScript ECMAScript 6 i dalej

background image
background image

Tytuł oryginału: You Don't Know JS: ES6 & Beyond

Tłumaczenie: Piotr Rajca

ISBN: 978-83-283-2311-7

© 2016 Helion SA

Authorized Polish translation of the English edition You Don't Know JS: ES6 & Beyond ISBN
9781491904244 © 2016 Getify Solutions, Inc.

This translation is published and sold by permission of O’Reilly Media, Inc.,
which owns or controls all rights to publish and sell the same.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from the Publisher.

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.

Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane
z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie
ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.

Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail:

helion@helion.pl

WWW:

http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/tajnjs
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

Printed in Poland.

Kup książkę

Poleć książkę

Oceń książkę

Księgarnia internetowa

Lubię to! » Nasza społeczność

background image

3

Spis treści

Przedmowa .......................................................................................................................5

Wstęp ...............................................................................................................................7

1. ES? Teraźniejszość i przyszłość .......................................................................................... 11

Wersje języka

12

Transpilacja — transformacja kodu

13

Biblioteki typu shim i polyfill

14

Podsumowanie 15

2. Składnia

.......................................................................................................................... 17

Deklaracje zakresu bloku

17

Rozproszenie (reszta)

24

Domyślne wartości parametrów

26

Destrukturyzacja 30
Rozszerzenia literałów obiektowych

44

Literały szablonów

51

Funkcje typu arrow function

57

Pętle for .. of

62

Wyrażenia regularne

65

Rozszerzenia literałów liczbowych

73

Unicode 74
Symbole 79
Podsumowanie 84

3. Organizacja ..................................................................................................................... 85

Iteratory 85
Generatory 95
Moduły 110
Klasy

126

Podsumowanie 136

Poleć książkę

Kup książkę

background image

4

_ Spis

treści

4. Asynchroniczne

sterowanie

przepływem ........................................................................137

Obietnice 137
Generatory i obietnice

143

Podsumowanie 146

5. Kolekcje

.........................................................................................................................147

Tablice określonego typu

147

Mapy

152

Mapy typu WeakMap

156

Zbiory 157
Zbiory typu WeakSet

158

Podsumowanie 159

6. Modyfikacje

API .............................................................................................................161

Array

161

Object 170
Math

173

Number 175
String

178

Podsumowanie 179

7. Metaprogramowanie

.....................................................................................................181

Nazwy funkcji

181

Dobrze znane symbole

185

Obiekty pośredniczące — Proxy

190

Interfejs API obiektu Reflect

202

Testowanie możliwości

206

Optymalizacja TCO

208

Podsumowanie 215

8. Dalszy rozwój języka po ES6 ...........................................................................................217

Funkcje asynchroniczne

218

Object.observe(..) 221
Operator potęgi

224

Właściwości obiektów i operator ...

224

Array#includes(..) 225
SIMD

226

WebAssembly (WASM)

227

Podsumowanie 229

A Podziękowania ..............................................................................................................231

Skorowidz .....................................................................................................................235

Poleć książkę

Kup książkę

background image

147

ROZDZIAŁ 5.

Kolekcje

Kolekcje oraz dostęp do danych o określonej strukturze to kluczowe elementy niemal wszystkich pro-
gramów pisanych w języku JavaScript. Od samego początku istnienia języka aż do chwili obecnej
podstawowymi mechanizmami używanymi do tworzenia struktur danych są tablice i obiekty. Oczywi-
ście, powstawały także biblioteki, które korzystając z tablic i obiektów, implementowały struktury
danych wyższego poziomu.

Jednak w ES6 niektóre najbardziej przydatne (i zoptymalizowane pod względem wydajności działa-
nia!) spośród tych abstrakcji danych zostały dodane jako macierzyste elementy języka.

Zaczniemy od przedstawienia tablic określonego typu (ang. TypedArrays), rozwiązania wprowadzo-
nego kilka lat temu wraz z udostępnieniem wersji ES5, lecz zestandaryzowanego wyłącznie jako
dodatek do WebGL, a nie do samego języka JavaScript. W przypadku ES6 tablice określonego typu zo-
stały dodane bezpośrednio do specyfikacji języka, co nadało im najwyższy status.

Mapy przypominają nieco obiekty (pary klucz-wartość), jednak w ich przypadku kluczem może być
nie tylko łańcuch znaków, lecz dowolna wartość — nawet inny obiekt lub mapa! Zbiory są podobne
do tablic (list wartości), ale umieszczane w nich wartości są unikalne; w przypadku próby dodania
duplikatu zostanie on pominięty. Istnieją także „słabe” (pod względem odzyskiwania i oczyszczania
pamięci) odpowiedniki tych dwóch struktur danych: WeakMap oraz WeakSet.

Tablice określonego typu

Zgodnie z informacjami podanymi w książce Tajniki języka JavaScript. Typy i gramatyka, należącej do
tej serii wydawniczej, język JavaScript posiada zestaw wbudowanych typów danych, takich jak

number

oraz

string

. Kuszące byłoby przypuszczenie, że termin „tablice określonego typu” oznacza tablicę

wartości pewnego konkretnego typu, na przykład: tablicę zawierającą wyłącznie łańcuchy znaków.

Jednak w przypadku tablic określonego typu chodzi raczej o zapewnienie strukturalnego dostępu do
danych binarnych przy wykorzystaniu składni charakterystycznej dla tablic (dostępu z użyciem
indeksów itd.). Słowo „typ” w nazwie tego mechanizmu odnosi się raczej do „widoku” prezentującego
zbiory bitów, stanowiącego w zasadzie odwzorowanie określające, czy bity powinny być traktowane
jako tablica 8-bitowych liczb całkowitych ze znakiem, czy też jako 16-bitowe liczby całkowite ze
znakiem itd.

Poleć książkę

Kup książkę

background image

148

_

Rozdział 5. Kolekcje

W jaki sposób tworzymy taki „zbiór bitów”. Jest on nazywany „buforem”, a najbardziej bezpośredni
sposób jego tworzenia polega na użyciu konstruktora

ArrayBuffer(..)

:

var buf = new ArrayBuffer( 32 );
buf.byteLength; // 32

Po wykonaniu powyższych instrukcji zmienna

buf

będzie zawierać bufor binarny o długości 32 bajtów

(czyli 256 bitów), który początkowo został wypełniony zerami. Sam bufor nie daje nam żadnych
możliwości interakcji z jego zawartością, a jedyną operacją, jaką możemy na nim wykonać, jest
sprawdzenie jego wielkości przy użyciu właściwości

byteLength

.

Kilka mechanizmów różnych platform internetowych korzysta z takich buforów
lub je zwraca; należą do nich:

FileReader#readAsArrayBuffer(..)

,

XMLHttpRequest#

´

send(..)

oraz

ImageData

(dane płócien).

Jednak na taki bufor możemy następnie nałożyć „widok”, którym jest właśnie tablica określonego
typu. Przyjrzyjmy się poniższemu przykładowi:

var arr = new Uint16Array( buf );
arr.length; // 16

Zmienna

arr

jest teraz tablicą określonego typu, prezentującą zawartość 256-bitowego bufora

buff

jako 16-bitowe liczby całkowite bez znaku, co oznacza, że będzie ona miała 16 elementów.

Kolejność zapisu bajtów

Koniecznie należy zrozumieć i zapamiętać, że tablica

arr

jest odwzorowywana z wykorzystaniem

określonej kolejności zapisu bajtów (big-endian lub little-endian), odpowiadających ustawieniom
platformy, na której działa środowisko JavaScript. Ten fakt może być problemem, gdy dane binarne
zostaną przygotowane z użyciem innej kolejności zapisu bajtów, lecz muszą być interpretowane na
platformie, w której kolejność zapisu bajtów jest przeciwna.

Oba terminy (big-endian oraz little-endian) określają, czy najmniej znaczący bajt (kolekcja 8 bitów)
wielobajtowej liczby — takiej jak 16-bitowe liczby bez znaku, których użyliśmy w ostatnim przykła-
dzie — zostanie zapisany z prawej, czy z lewej strony sekwencji bajtów reprezentujących liczbę.

Wyobraźmy sobie liczbę dziesiętną

3085

, która musi być reprezentowana przy użyciu 16 bitów.

Gdyby taka liczba miała być reprezentowana przy użyciu jednego, 16-bitowego pojemnika, to nie-
zależnie od kolejności zapisu miałaby dwójkową postać

0000110000001101

(lub

0c0d

szesnastkowo).

Gdybyśmy jednak chcieli zapisać ją jako dwie liczby 8-bitowe, to zastosowana kolejność zapisu baj-
tów miałaby ogromne znaczenie dla postaci tej liczby umieszczonej w pamięci:

x

0000110000001101

/

0c0d

(w przypadku stosowania zapisu big-endian),

x

0000110100001100

/

0d0c

(w przypadku stosowania zapisu little-endian).

Jeśli pobraliśmy bity

0000110100001100

reprezentujące liczbę

3085

w systemie korzystającym z zapisu

little-endian, lecz spróbujemy ich użyć w systemie korzystającym z zapisu big-endian, to okaże się,
że uzyskamy liczbę

3340

(w systemie dziesiętnym) lub

0d0c

(w systemie szesnastkowym).

Poleć książkę

Kup książkę

background image

Tablice określonego typu

_ 149

Zapis little-endian jest obecnie najczęściej używaną w internecie reprezentacją danych, choć oczywi-
ście istnieją przeglądarki, w których nie jest on stosowany. Bardzo ważne jest, by zrozumieć, że zna-
czenie ma sposób zapisu danych binarnych używany zarówno przez ich producenta, jak i przez
konsumenta.

Oto prosta metoda sprawdzania używanej kolejności zapisu danych, przedstawiona na witrynie MDN:

var littleEndian = (function() {
var buffer = new ArrayBuffer( 2 );
new DataView( buffer ).setInt16( 0, 256, true );
return new Int16Array( buffer )[0] === 256;
})();

Zmienna

littleEndian

będzie przyjmować wartości

true

lub

false

, w większości przeglądarek

powinna mieć wartość

true

. Powyższy kod korzysta z konstruktora

DataView(..)

zapewniającego

najwyższy i najdokładniejszy poziom kontroli nad dostępnością (ustawianiem i pobieraniem)
bitów z widoku utworzonego na bazie bufora. Trzeci parametr metody

setInt16(..)

zastosowa-

nej w powyższym kodzie informuje

DataView

, jaka kolejność zapisu danych ma być zastosowana

w wykonywanej operacji.

Nie należy mylić kolejności zapisu danych binarnych przechowywanych w buforze
tablicy ze sposobem reprezentacji danej liczby po udostępnieniu jej w programie
JavaScript. Na przykład wywołanie

(3085).toString(2)

zwróci łańcuch znaków

"110000001101"

, który przy pominięciu czterech początkowych

0

wydaje się repre-

zentacją używającą zapisu little-endian. W rzeczywistości reprezentacja ta bazuje na
pojedynczym widoku 16-bitowym, a nie na widoku dwóch bajtów (po 8 bitów każdy).
Powyższe rozwiązanie stanowi najlepszy sposób sprawdzenia kolejności zapisu bajtów
stosowanej przez używane środowisko.

Wiele widoków

Do jednego bufora można dołączyć wiele różnych widoków. Oto przykład:

var buf = new ArrayBuffer( 2 );

var view8 = new Uint8Array( buf );
var view16 = new Uint16Array( buf );

view16[0] = 3085;
view8[0]; // 13
view8[1]; // 12

view8[0].toString( 16 ); // "d"
view8[1].toString( 16 ); // "c"

// zamiana (jakby zmiana kolejnoĞci zapisu!)
var tmp = view8[0];
view8[0] = view8[1];
view8[1] = tmp;

view16[0]; // 3340

Poleć książkę

Kup książkę

background image

150

_

Rozdział 5. Kolekcje

Konstruktory tablic określonego typu mają wiele różnych sygnatur. Na razie przedstawione zostały
tylko te, do których przekazywany jest sam bufor. Niemniej jednak ta forma konstruktorów umożliwia
także podanie dwóch opcjonalnych parametrów:

byteOffset

oraz

length

. Oznacza to, że tworzony

widok tablicy określonego typu może się zaczynać w innym miejscu niż wskazywane przez indeks

0

i może obejmować mniejszy zakres danych niż pełna długość bufora.

Technika ta może się okazać całkiem przydatna, jeśli dane binarne umieszczone w buforze nie
mają jednolitej wielkości bądź rozmieszczenia.

Załóżmy, że dysponujemy binarnym buforem, w którym najpierw jest zapisana liczba składająca się
z dwóch bajtów (tak zwane „słowo”), następnie dwie liczby jednobajtowe, a następnie liczba zmienno-
przecinkowa o długości czterech bajtów. Poniższy przykład pokazuje, w jaki sposób można pobrać
te wszystkie dane, korzystając z różnych widoków bazujących na tym samym buforze, lecz używają-
cych różnych przesunięć oraz długości:

var first = new Uint16Array( buf, 0, 2 )[0],
second = new Uint8Array( buf, 2, 1 )[0],
third = new Uint8Array( buf, 3, 1 )[0],
fourth = new Float32Array( buf, 4, 4 )[0];

Konstruktory tablic określonego typu

Oprócz formy z parametrami

(buffer, [offset, [length]])

, przedstawionej w poprzednich przy-

kładach, dostępne są także inne postaci konstruktorów tablic określonego typu:

x

[konstruktor]

(length)

— tworzy nowy widok na bazie nowego bufora o długości

length

.

x

[konstruktor]

(typedArr)

— tworzy nowy widok oraz nowy bufor, kopiując do niego zawartość

przekazanego widoku

typedArr

.

x

[konstruktor]

(obj)

— tworzy nowy widok oraz nowy bufor, do którego zostają skopiowane dane

pobrane podczas przeglądania obiektu lub obiektu przypominającego tablicę.

Na poniższej liście przedstawiłem wszystkie konstruktory tablic określonego typu dostępne w ję-
zyku ES6:

x

Int8Array

(8-bitowe liczby całkowite ze znakiem),

Uint8Array

(8-bitowe liczby całkowite bez

znaku),

x

Uint8ClampedArray

(8-bitowe liczby całkowite bez znaku, przy czym podczas inicjalizacji każda

wartość jest ograniczana do zakresu od 0 do 255),

x

Int16Array

(16-bitowe liczby całkowite ze znakiem),

Uint16Array

(16-bitowe liczby całkowite

bez znaku),

x

Int32Array

(32-bitowe liczby całkowite ze znakiem),

Uint32Array

(32-bitowe liczby całkowite

bez znaku),

x

Float32Array

(32-bitowe liczby zmiennoprzecinkowe, IEEE-754),

x

Float64Array

(64-bitowe liczby zmiennoprzecinkowe, IEEE-754).

Tablice określonego typu tworzone przy użyciu tych konstruktorów są niemal takie same jak zwyczaj-
ne, macierzyste tablice języka JavaScript. Różnica pomiędzy nimi polega na tym, że tablice określonego
typu mają stałą, niezmienną długość, a ich zawartość stanowią wartości tego samego „typu”.

Poleć książkę

Kup książkę

background image

Tablice określonego typu

_ 151

Poza tymi rozbieżnościami oba rodzaje tablic dysponują tymi samymi metodami udostępnianymi
przez ich konstruktory. Z tego względu zapewne będziemy mogli używać ich jako zwyczajnych
tablic bez konieczności przeprowadzania jakichkolwiek konwersji.

Oto przykład:

var a = new Int32Array( 3 );
a[0] = 10;
a[1] = 20;
a[2] = 30;

a.map( function(v){
console.log( v );
} );
// 10 20 30

a.join( "-" );
// "10-20-30"

Podczas korzystania z tablic określonego typu nie można używać niektórych metod

Array.prototype

; chodzi o metody, których zastosowanie nie ma w danym przypadku

sensu, takie jak mutatory (

splice(..)

,

push(..)

itd.) oraz metoda

concat(..)

.

Trzeba pamiętać, że wielkość elementów tablic określonego typu jest ograniczona przez zadeklaro-
wany typ tablicy. Jeśli używamy tablicy

Uint8Array

i spróbujemy zapisać w jednym z jej elemen-

tów daną, której wielkość przekracza 8 bitów, to zostanie ona przycięta tak, by liczba bajtów zapi-
sywanej wartości odpowiadała zadeklarowanej wielkości elementów tablicy.

W niektórych przypadkach, na przykład w razie próby podniesienia do kwadratu liczb zapisanych
w tablicy określonego typu, może to być przyczyną problemów. Przeanalizujmy następujący przykład:

var a = new Uint8Array( 3 );
a[0] = 10;
a[1] = 20;
a[2] = 30;

var b = a.map( function(v){
return v * v;
} );

b; // [100, 144, 132]

Podniesienie do kwadratu wartości

20

oraz

30

powoduje wystąpienie przepełnienia bitowego. Ogra-

niczenie to można ominąć, korzystając z funkcji

TypedArray

#from(..)

:

var a = new Uint8Array( 3 );
a[0] = 10;
a[1] = 20;
a[2] = 30;

var b = Uint16Array.from( a, function(v){
return v * v;
} );

b; // [100, 400, 900]

Poleć książkę

Kup książkę

background image

152

_

Rozdział 5. Kolekcje

Więcej informacji na temat funkcji

Array.from(..)

, dostępnej także w tablicach określonego typu,

można znaleźć w rozdziale 6., w punkcie „Funkcja statyczna Array.from(..)”. W podpunkcie „Od-
wzorowania” opisana została funkcja odwzorowująca, którą można przekazać jako drugi argument
funkcji

Array.from(..)

.

Jedną z interesujących cech tablic określonego typu jest udostępnianie metody

sort(..)

, analogicz-

nej do metody dostępnej w normalnych tablicach języka JavaScript. Jednak w przypadku tablic
określonego typu metoda ta domyślnie porównuje liczby, a nie konwertuje wartości do postaci łańcu-
chowej w celu wykonania porównania alfabetycznego. Oto przykład zastosowania obu tych metod:

var a = [ 10, 1, 2, ];
a.sort(); // [1,10,2]

var b = new Uint8Array( [ 10, 1, 2 ] );
b.sort(); // [1,2,10]

Metoda

TypedArray

#sort(..)

dysponuje także drugim, opcjonalnym parametrem — można go użyć

do przekazania funkcji porównującej, która działa dokładnie tak samo jak funkcje porównujące sto-
sowane w metodzie

Array#sort(..)

.

Mapy

Jeśli masz duże doświadczenie w programowaniu w języku JavaScript, to zapewne wiesz, że obiekty
są podstawowym mechanizmem do tworzenia struktur danych zawierających nieuporządkowane
pary klucz-wartość, nazywanych także mapami. Niemniej jednak podstawową wadą związaną
z tworzeniem map przy wykorzystaniu obiektów jest to, że kluczami obiektów mogą być wyłącznie
łańcuchy znaków.

Przeanalizujmy następujący przykład:

var m = {};

var x = { id: 1 },
y = { id: 2 };

m[x] = "foo";
m[y] = "bar";

m[x]; // "bar"
m[y]; // "bar"

Co się tu dzieje? Otóż oba obiekty,

x

i

y

, zostają przekształcone do postaci łańcucha

"[object Object]"

,

dlatego w obiekcie

m

zostanie utworzony tylko jeden klucz.

Niektórzy implementowali fałszywe mapy, tworząc dwie tablice, z których jedna zawierała klucze
niebędące łańcuchami znaków, a druga — wartości. Oto przykład takiego rozwiązania:

var keys = [], vals = [];

var x = { id: 1 },

y = { id: 2 };

keys.push( x );
vals.push( "foo" );

Poleć książkę

Kup książkę

background image

Mapy

_ 153

keys.push( y );
vals.push( "bar" );

keys[0] === x; // true
vals[0]; // "foo"

keys[1] === y; // true
vals[1]; // "bar"

Oczywiście nikt nie chciałby własnoręcznie zarządzać takimi równoległymi tablicami, dlatego za-
pewne warto by zdefiniować strukturę danych, której metody ukrywałyby szczegóły implementa-
cyjne związane z zarządzaniem tymi tablicami. Oprócz konieczności zaimplementowania takiego
rozwiązania jego kolejną podstawową wadą jest to, że złożoność działania takiej struktury danych
nie jest już liniowa — O(1) — lecz wynosi O(n).

Na szczęście w języku ES6 nie ma już potrzeby stosowania takich rozwiązań! Wystarczy utworzyć
mapę —

Map(..)

:

var m = new Map();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );
m.set( y, "bar" );

m.get( x ); // "foo"
m.get( y ); // "bar"

Jedyną wadą jest tu brak możliwości ustawiania i pobierania wartości mapy przy użyciu zapisu
z nawiasami kwadratowymi (

[ ]

), charakterystycznego dla tablic. Zamiast tego operacje te trzeba

wykonywać odpowiednio przy użyciu metod

set(..)

i

get(..)

.

Do usuwania elementów mapy nie należy używać operatora

delete

, lecz metody

delete(..)

:

m.set( x, "foo" );
m.set( y, "bar" );

m.delete( y );

Całą zawartość mapy można usunąć przy użyciu metody

clear()

. Z kolei długość mapy (czyli

liczbę jej kluczy) można pobrać, używając właściwości

size

(nie

length

):

m.set( x, "foo" );
m.set( y, "bar" );
m.size; // 2

m.clear();
m.size; // 0

W wywołaniu konstruktora

Map(..)

można także przekazać obiekt iterowalny (więcej informacji

na temat takich obiektów podałem w rozdziale 3., w podrozdziale „Iteratory”), który musi zwracać li-
stę tablic, przy czym pierwszy element każdej tablicy jest kluczem, a drugi — skojarzoną z nim
wartością. Ten format iteracji idealnie odpowiada efektom działania metody

entries()

, opisanej

w następnym punkcie rozdziału. Dzięki temu bardzo łatwo można tworzyć kopie map:

Poleć książkę

Kup książkę

background image

154

_

Rozdział 5. Kolekcje

var m2 = new Map( m.entries() );

// ma ten sam efekt co:
var m2 = new Map( m );

Ponieważ instancja mapy jest obiektem iterowalnym, a jej domyślny iterator działa tak samo jak
metoda

entries()

, to preferowany jest drugi, krótszy sposób wywoływania konstruktora.

Oczywiście istnieje także możliwość ręcznego przekazania do konstruktora

Map(..)

listy elemen-

tów mapy (czyli tablicy dwuelementowych tablic zawierających pary klucz-wartość). Takie roz-
wiązanie przedstawia poniższy przykład:

var x = { id: 1 },
y = { id: 2 };

var m = new Map( [
[ x, "foo" ],
[ y, "bar" ]
] );

m.get( x ); // "foo"
m.get( y ); // "bar"

Wartości map

W celu pobrania wartości mapy należy wywołać metodę

values(..)

, która zwraca iterator. W roz-

działach 2. i 3. poznaliśmy różne metody sekwencyjnego przetwarzania iteratorów (czyli podob-
nego do sposobów korzystania z tablic), na przykład z użyciem operatora

...

lub pętli

for .. of

.

Informacje na temat metody

Array.from(..)

można także znaleźć w rozdziale 6., w punkcie

„Tworzenie tablic i podtypów”. Przeanalizujmy poniższy przykład:

var m = new Map();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );
m.set( y, "bar" );

var vals = [ ...m.values() ];

vals; // ["foo","bar"]
Array.from( m.values() ); // ["foo","bar"]

Zgodnie z informacjami podanymi w poprzednim rozdziale, elementy mapy można przejrzeć,
korzystając z metody

entries()

(bądź domyślnego iteratora mapy). Oto przykład wykorzystania

tej metody:

var m = new Map();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );
m.set( y, "bar" );

var vals = [ ...m.entries() ];

Poleć książkę

Kup książkę

background image

Mapy

_ 155

vals[0][0] === x; // true
vals[0][1]; // "foo"

vals[1][0] === y; // true
vals[1][1]; // "bar"

Klucze map

W celu pobrania wartości mapy należy wywołać metodę

keys(..)

, która zwraca iterator udostęp-

niający wszystkie klucze mapy:

var m = new Map();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );
m.set( y, "bar" );

var keys = [ ...m.keys() ];

keys[0] === x; // true
keys[1] === y; // true

Z kolei metoda

has(..)

pozwala sprawdzić, czy mapa zawiera konkretny klucz:

var m = new Map();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );

m.has( x ); // true
m.has( y ); // false

W gruncie rzeczy mapy pozwalają nam skojarzyć obiektem (kluczem) jakąś informację (wartość)
bez faktycznego zapisywania tej informacji w danym obiekcie.

Choć kluczami map mogą być dowolne wartości, to jednak zazwyczaj będą nimi obiekty, gdyż łań-
cuchy znaków oraz wartości innych typów prostych mogą być stosowane jako klucze zwyczajnych
obiektów. Innymi słowy, najprawdopodobniej wciąż będziemy używali zwyczajnych obiektów za-
miast map, wyjąwszy sytuacje, gdy niektóre lub wszystkie klucze obiektu także muszą być obiektami
— zastosowanie mapy będzie wówczas lepszym rozwiązaniem.

Jeśli użyjemy obiektu jako klucza mapy, a następnie obiekt ten zostanie porzucony
(zostaną usunięte wszystkie odwołania do niego) w ramach próby odzyskania zajmo-
wanej przez niego pamięci, to element mapy nie zostanie usunięty. W celu umożli-
wienia odzyskania pamięci zajmowanej przez element mapy trzeba go z niej jawnie
usunąć. W następnym punkcie, poświęconym mapom typu

WeakMap

, poznamy lepsze

rozwiązanie, jeśli chodzi o stosowanie obiektów jako kluczy i odzyskiwanie pamięci.

Poleć książkę

Kup książkę

background image

156

_

Rozdział 5. Kolekcje

Mapy typu WeakMap

Tak zwane „słabe mapy”,

WeakMap

, to specjalny rodzaj map, który dysponuje w zasadzie takim samym

zewnętrznym zachowaniem jak normalne mapy, lecz różni się od nich pod względem wewnętrznego
sposobu przydzielania pamięci (a zwłaszcza możliwości jej odzyskiwania).

W przypadku map typu

WeakMap

kluczami mogą być wyłącznie obiekty. Obiekty te są przechowywane

w słaby sposób, co oznacza, że w przypadku ich usunięcia przez mechanizm odzyskiwania pamięci
(GC) usunięty zostanie także cały element mapy. Tego działania nie można jednak zaobserwo-
wać, gdyż jedynym sposobem, by mechanizm odzyskiwania pamięci usunął obiekt, jest usunięcie
wszystkich referencji do tego obiektu — to z kolei oznacza, że w programie nie będzie żadnych
referencji, które pozwoliłyby sprawdzić, czy dany obiekt istnieje w mapie, czy nie.

Pod wszelkimi innymi względami interfejs API mapy typu

WeakMap

w dużym stopniu przypomina

interfejs zwyczajnych map, choć jest nieco bardziej ograniczony:

var m = new WeakMap();

var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );

m.has( x ); // true
m.has( y ); // false

Mapy typu

WeakMap

nie udostępniają właściwości

size

, metody

clear()

ani iteratorów pozwalających

na przeglądanie ich kluczy, wartości oraz elementów. A zatem nawet jeśli usuniemy referencję

x

,

powodując tym samym usunięcie skojarzonego z nią elementu mapy

m

podczas następnego cyklu

odzyskiwania pamięci, to i tak nie będziemy mogli się o tym przekonać. Pozostanie nam jedynie
wierzyć na słowo!

Mapy typu

WeakMap

, podobnie jak zwyczajne mapy, pozwalają na swoiste kojarzenie informacji

z obiektami. Są one szczególnie przydatne, kiedy nie dysponujemy całkowitą kontrolą nad używa-
nymi obiektami, na przykład gdy są nimi elementy DOM. A zatem, jeśli obiekty używane jako
klucze map mogą być usuwane i zajmowana przez nie pamięć powinna zostać zwolniona w ramach
procesu odzyskiwania pamięci (GC), to zastosowanie mapy typu

WeakMap

będzie optymalnym

rozwiązaniem.

Koniecznie należy zapamiętać, że w przypadku map typu

WeakMap

jedynie klucze są przechowy-

wane w „słaby” sposób — nie dotyczy to wartości mapy. Przeanalizujmy poniższy przykład:

var m = new WeakMap();

var x = { id: 1 },
y = { id: 2 },
z = { id: 3 },
w = { id: 4 };

m.set( x, y );

x = null; // { id: 1 } moĪe zostaü usuniĊty przez GC
y = null; // { id: 2 } moĪe zostaü usuniĊty przez GC
// ale tylko dlatego, Īe dotyczy to takĪe obiektu { id: 1 }

Poleć książkę

Kup książkę

background image

Zbiory

_ 157

m.set( z, w );
w = null; // { id: 4 } nie moĪe zostaü usuniĊty przez GC

Właśnie z tego powodu uważam, że odpowiedniejszą nazwą dla map tego typu byłoby

WeakKeyMap

— „mapy o słabych kluczach”.

Zbiory

Zbiory są kolekcjami unikalnych wartości (wszelkie duplikaty są pomijane).

Interfejs API zbiorów przypomina interfejs map. Zamiast metody

set(..)

zbiory używają metody

add(..)

(co można by potraktować jako swoisty żart), a poza tym nie udostępniają metody

get(..)

.

Przeanalizujmy poniższy przykład:

var s = new Set();

var x = { id: 1 },
y = { id: 2 };

s.add( x );
s.add( y );
s.add( x );

s.size; // 2

s.delete( y );
s.size; // 1

s.clear();
s.size; // 0

Konstruktor

Set(..)

przypomina nieco konstruktor

Map(..)

, gdyż tak jak w przypadku map,

można do niego przekazać daną iterowalną, taką jak inny zbiór lub zwyczajna tablica wartości.
Jednak w odróżnieniu od konstruktora

Map(..)

, do którego należy przekazać listę elementów (ta-

blicę tablic reprezentujących pary klucz-wartość), konstruktor

Set(..)

oczekuje przekazania listy

wartości (tablicy wartości):

var x = { id: 1 },
y = { id: 2 };

var s = new Set( [x,y] );

Zbiory nie potrzebują metody

get(..)

, gdyż w ich przypadku nie wykonujemy operacji pobiera-

nia wartości ze zbioru, a jedynie sprawdzamy, czy konkretna wartość w nim występuje:

var s = new Set();

var x = { id: 1 },
y = { id: 2 };

s.add( x );

s.has( x ); // true
s.has( y ); // false

Poleć książkę

Kup książkę

background image

158

_

Rozdział 5. Kolekcje

Algorytm porównywania używany przez metodę

has(..)

jest niemal identyczny

z algorytmem stosowanym przez funkcję

Object.is(..)

(informacje na jego temat

można znaleźć w rozdziale 6.), a jedyna różnica polega na tym, że wartości

-0

i

0

traktowane jako identyczne, a nie różne.

Iteratory zbiorów

Zbiory udostępniają te same metody zwracające iteratory co mapy. W przypadku zbiorów działanie
tych metod jest nieco inne, choć analogiczne do działania iteratorów map. Przeanalizujmy nastę-
pujący przykład:

var s = new Set();

var x = { id: 1 },
y = { id: 2 };

s.add( x ).add( y );

var keys = [ ...s.keys() ],
vals = [ ...s.values() ],
entries = [ ...s.entries() ];

keys[0] === x;
keys[1] === y;

vals[0] === x;
vals[1] === y;

entries[0][0] === x;
entries[0][1] === x;
entries[1][0] === y;
entries[1][1] === y;

Iteratory

keys()

oraz

values()

zwracają listę wszystkich unikalnych wartości zbioru. Z kolei iterator

entries()

zwraca listę tablic reprezentujących elementy zbioru, przy czym oba elementy tych ta-

blic są unikalną wartością zbioru. Domyślnym iteratorem zbiorów jest

values()

.

Najużyteczniejszą cechą zbiorów jest charakterystyczna dla nich unikalność. Przeanalizujmy po-
niższy przykład:

var s = new Set( [1,2,3,4,"1",2,4,"5"] ),
uniques = [ ...s ];

uniques; // [1,2,3,4,"1","5"]

Mechanizm określania unikalności wartości zapisywanych w zbiorze nie korzysta z konwersji typów,
dlatego

1

i

"1"

są traktowane jako odrębne, unikalne wartości.

Zbiory typu WeakSet

W odróżnieniu od map typu

WeakMap

, które w „słaby” sposób przechowują klucze (wartości są nato-

miast przechowywane „mocno”), zbiory typu

WeakSet

w „słaby” sposób przechowują wartości

(klucze w zasadzie nie są w nich stosowane).

Poleć książkę

Kup książkę

background image

Podsumowanie

_ 159

var s = new WeakSet();

var x = { id: 1 },
y = { id: 2 };

s.add( x );
s.add( y );

x = null; // obiekt 'x' moĪe zostaü usuniĊty przez GC
y = null; // obiekt 'y' moĪe zostaü usuniĊty przez GC

Wartościami zbiorów typu

WeakSet

muszą być obiekty — w odróżnieniu od zwyczaj-

nych zbiorów zapisywanie w nich wartości typów prostych nie jest dozwolone.

Podsumowanie

W języku ES6 dodanych zostało kilka użytecznych kolekcji, dzięki którym operowanie na danych
w strukturalny sposób stało się prostsze i wydajniejsze.

Tablice określonego typu tworzą „widoki” buforów zawierających dane binarne, dzięki którym
dane te są prezentowane jako wartości różnych typów liczbowych, na przykład: 8-bitowe liczby całko-
wite bez znaku lub 32-bitowe liczby zmiennoprzecinkowe. Możliwość dostępu do danych binar-
nych z użyciem zapisu tablicowego sprawia, że wykonywane operacje można zapisać w znacznie
prostszej postaci, ułatwiając tym samym pielęgnację kodu oraz korzystanie ze złożonych danych,
takich jak dane audiowizualne, dane płócien itd.

Mapy są kolekcjami para klucz-wartość, przy czym pozwalają, by kluczami były także obiekty, a nie
tylko wartości typów prostych i łańcuchy znaków. Zbiory są listami unikalnych wartości (dowolnego
typu).

Mapy typu

WeakMap

przechowują swoje klucze (obiekty) w słaby sposób, dzięki czemu mechanizm

odzyskiwania pamięci może usunąć element mapy, jeśli to ona zawiera ostatnią referencję do danego
klucza. Z kolei zbiory typu

WeakSet

w podobny — słaby — sposób przechowują swoje wartości,

dzięki czemu, jeśli w zbiorze jest przechowywana ostatnia referencja do obiektu, to odpowiadający mu
element zbioru może zostać usunięty przez mechanizm odzyskiwania pamięci.

Poleć książkę

Kup książkę

background image

160

_

Rozdział 5. Kolekcje

Poleć książkę

Kup książkę

background image

235

Skorowidz

A

AMD, Asynchronous Module Definition, 111
API, 161
asynchroniczne sterowanie przepływem, 137

B

biblioteka FeatureTests.io, 208
biblioteki

typu polyfill, 14
typu shim, 14

błąd tymczasowej martwej strefy, 20
błędy, 106

C

CommonJS, 113

D

deklaracja

const, 22
let, 18, 21

deklaracje zakresu bloku, 17
delegacja zwracania wartości, 99
destrukturyzacja, 30, 41

obiektów, 31
parametrów, 38
tablicy, 31
zagnieżdżona, 38

domyślne wartości parametrów, 26
dopasowywanie globalne, 70

E

eksport

domyślny, 115
nazwany, 114

eksportowanie interfejsów API, 113
ES5, 11
ES6, 217

F

flaga

sticky, 67
Unicode, 66

funkcja

Array.from(..), 162, 164
Array.of(..), 161
indexOf(..), 225
Number.isFinite(..), 176
Number.isNaN(..), 175
Object.assign(..), 172
Object.getOwnPropertySymbols(..), 171
Object.is(..), 170
Object.observe(..), 221
Object.setPrototypeOf(..), 171
Object.unobserve(..), 224
repeat(..), 179
String.raw(..), 178
Symbol.species(..), 135

funkcje

asynchroniczne, 218
matematyczne, 173
o zakresie bloku, 23
sprawdzania łańcuchów znaków, 179

Poleć książkę

Kup książkę

background image

236

_ Skorowidz

funkcje

statyczne, 170, 175
statyczne związane z liczbami całkowitymi, 176
typu arrow function, 57, 63
związane z Unicode, 178

G

generatory, 95, 143
generowanie sekwencji wartości, 109

I

import przestrzeni nazw, 120
importowanie interfejsów API, 118
interfejs, 86

API, 113, 202
API obietnic, 141

iteracje, 87
iteratorResult, 86
iteratory, 85

iteratory niestandardowe, 90
zbiorów, 158

J

jawność, 21

K

klasy, 126
klucze map, 155
kolejność

właściwości, 204
zapisu bajtów, 148

kolekcje, 147
konstruktory

klas pochodnych, 131
tablic, 150

kontrola iteratora, 96, 100

L

literały

liczbowe, 73
szablonów, 51
szablonów ze znacznikiem, 54

Ł

łańcuch, 199

magiczny znaków, 82
znaków nieprzetworzony, 57

M

mapy, 152

klucze, 155
typu WeakMap, 156
wartości, 154

Math, 173
metaprogramowanie, 181
metawłaściwość, 133, 184
metoda

array#includes(..), 225
copyWithin(..), 166
entries(), 169
fill(..), 167
find(..), 167
findIndex(..), 168
keys(), 169
next(), 87
notifier.notify(..), 223
Promise.reject(..), 142
then(..), 139
values(), 169

metody

opcjonalne, 88
prototypu, 166, 169
statyczne, 134
typu getter, 48
typu setter, 48
zwięzłe, 44

moduł

sposoby wczytywania, 126
wczytywanie, 124

moduły, 110
modyfikacje API, 161

N

nazwy

funkcji, 181
identyfikatorów, 79

new.target, 133
Number, 175

Poleć książkę

Kup książkę

background image

Skorowidz

_ 237

O

obiekt

Math, 173
Number, 175
Object, 170
Reflect, 202
String, 178

obiekty

obsługi, 191
pośredniczące, 190

jako ostatnie, 195
jako pierwsze, 195
ograniczenia, 194
z możliwością unieważnienia, 194
zastosowania, 195

thenable, 140

obietnice, 137, 143
obliczane nazwy właściwości, 49
obsługa błędów, 106
odwzorowania, 164
ograniczenia obiektów pośredniczących, 194
OLOO, 11
operator potęgi, 224
optymalizacja, 211

TCO, 208, 213

P

parametr, 26
pętla

for .. of, 62
iteratora, 89

pozycja ogonowa, 210
pozycjonowanie

w trybie sticky, 68
znaków, 77

Proxy, 190
przepisywanie wywołania ogonowego, 210
przetwarzane literały łańcuchowe, 52
przypisania powtarzane, 35
przypisanie

destrukturyzacji, 35
wartości domyślnej, 37
właściwości obiektu, 31

pułapki, 190
puste komórki, 164

R

ramka stosu, 208
rejestr symboli, 81
rekurencja, 212
restrukturyzacja, 41
reszta, 24
rozproszenie, 24
rozszerzanie obiektów, 132
rozszerzenia literałów

liczbowych, 73
obiektowych, 44

rozwijanie rekurencji, 212

S

SIMD, 226
singleton, 81
składnia, 17
słowo kluczowe

class, 126
extends, 128
super, 51, 128
yield, 97

specyfikator modułu, 119
sprytne łańcuchy, 52
stała, 22
standard ES5, 11
sterowanie przepływem, 137
stosowanie

const, 23
iteratorów, 94
obiektów pośredniczących, 195
obietnic, 138

String, 178
strukturalne przypisanie, 30
Symbol.hasInstance, 186
Symbol.isConcatSpreadable, 189
Symbol.iterator, 185
Symbol.species, 187
Symbol.toPrimitive, 187
Symbol.toStringTag, 186
Symbol.unscopables, 190
symbole, 79, 83, 185

wbudowane, 83
wyrażeń regularnych, 188

Poleć książkę

Kup książkę

background image

238

_ Skorowidz

T

tablice określonego typu, 147
TDZ, Temporal Dead Zone, 20
testowanie możliwości, 206
trampolina, 211
transformacja kodu, 13
transpilacja, 13

generatora, 108

tryb sticky, 70, 71
tworzenie

kolejki zadań, 110
obietnic, 138
podtypów, 165
tablic, 165

tymczasowa martwa strefa, TDZ, 20

U

Unicode, 74
ustawienie [[Prototype]], 50

W

wartości

domyślne destrukturyzacji, 40
domyślne parametrów, 40
domyślne zagnieżdżone, 41
map, 154
parametrów, 26

wartość NaN, 226
WASM, 227
wczesne

przerwanie, 105
zakończenie, 103

wczytywanie modułów, 124
WebAssembly, 227

wersje języka, 12
wiązanie this, 60
widok, 149
właściwości

obiektów, 224
statyczne, 175
zwięzłe, 44

wnioskowanie, 183
wyrażenia, 28

interpolowane, 53
regularne, 65

wyrażenie

yield, 96
yield*, 99

wywołania ogonowe, 209, 210
wzorzec przypisania, 31

Z

zakotwiczenie, 71
zakres

jawny bloku, 17, 19
wyrażeń, 54

zależności

cykliczne, 123
pomiędzy modułami, 123

zapis bajtów, 148
zastosowania generatorów, 109
zbiory, 157

typu WeakSet, 158

zdarzenia niestandardowe zmian, 222
znacznik, 55
znaki

diakrytyczne, 76
Unicode, 74

Poleć książkę

Kup książkę

background image
background image

Wyszukiwarka

Podobne podstrony:
Tajniki jezyka JavaScript ECMAScript 6 i dalej
Tajniki jezyka JavaScript Na drodze do bieglosci
Tajniki jezyka JavaScript Na drodze do bieglosci
Tajniki jezyka JavaScript Na drodze do bieglosci
Zaawansowane techniki jezyka JavaScript Wydanie II
Zaawansowane techniki jezyka JavaScript Wydanie II ztejs2
Zaawansowane techniki jezyka JavaScript Wydanie II ztejs2
Tworzenie animacji z uzyciem jezyka JavaScript
Tworzenie animacji z uzyciem jezyka JavaScript 2
Tworzenie animacji z uzyciem jezyka JavaScript
Zaawansowane techniki jezyka JavaScript Wydanie II
[JAVASCRIPT][ECMAScript 6 (ES6) guide]
jotesy, JS, JavaScript to nazwa języka programowania opracowanego przez frimy Sun Microsystems i Net
Efektywny JavaScript 68 sposobow wykorzystania potegi jezyka
Profesjonalne tworzenie gier internetowych dla systemu Android w jezykach HTML5 CSS3 i JavaScript pr

więcej podobnych podstron