ES6 – strings
Kolejnym przystankiem na drodze do poznania ECMAScript2015 jest zapoznanie się z nowościami dotyczącymi łańcuchów znaków. Następna wersja języka JavaScript, wprowadza kilka bardzo ważnych udoskonaleń, które postaram się opisać w niniejszym wpisie.
Zobacz całą serię: Let-s talk about ECMAScript 2015
template strings
W oparciu o interpolację możemy wreszcie tworzyć szablony, które uzupełniane będą wartościami zmiennych. Można rzec: w końcu!
let x = 1; let y = 2; let sumTpl = `${x} + ${y} = ${x + y}`; console.log(sumTpl); // 1 + 2 = 3
Jak widać, wyłuskiwanie wartości w takim szablonie następuje poprzez użycie konstrukcji ${value}. Poza tym całe wyrażenie musi być zbudowane z użyciem znaku akcentu (`), by całość rozpoznana była jako szablon.
Powyższy przykład w ES5 przedstawia się następująco:
var x = 1; var y = 2; var sumTpl = "" + x + " + " + y + " = " + (x + y); console.log(sumTpl); // 1 + 2 = 3
Znacznie lepiej wygląda sprawa z ECMAScript2015. Nie potrzebujemy już żadnych narzędzi pozwalających budować szablony.
Łańcuchy znaków są od teraz jeszcze bardziej użyteczne. Pozwalają na definiowanie wartości w wielu liniach, bez użycia znaków konkatenacji:
let types = `Number String Array Object`; console.log(types); // Number // String // Array // Object
Z wykorzystaniem składni ES5 napisalibyśmy pewnie coś takiego:
var types = "Number\\nString\\nArray\\nObject"; console.log(types); // Number // String // Array // Object
Obiekt String posiada od teraz również nową własność – raw. Pozwala ona pominąć interpretację znaków \\ w wyrażeniu typu multi-line:
let interpreted = 'raw\\nstring'; let esaped = 'raw\\\\nstring'; let raw = String.raw`raw\\nstring`; console.log(interpreted); // raw // string console.log(raw === esaped); // true
Unicode
Wraz z ES6, otrzymujemy pełne wsparcie dla standardu Unicode, mającego obejmować wszystkie znaki znane na całym świecie.
Skrótu ES6 używam w zasadzie naprzemiennie z ECMAScript2015. Kolejna wersja języka JavaScript oznaczona jest numerem 6, który przyjął się w środowisku deweloperów znacznie szybciej niż pojawiła się pełna nazwa nowego standardu.
Spójrzmy na przykład:
let str = '𠮷'; console.log(str.length); // 2 console.log(str === '\\uD842\\uDFB7'); // true
Widzimy, że znak 𠮷 reprezentowany jest przez dwa 16-bitowe znaki kodowe. Tak więc mamy znak, który potrzebuje aż dwóch znaków kodowych do poprawnego zapisania. Mimo iż widzimy tylko jeden znak, jego długość wynosi 2.
Termin surrogate pairs jest ściśle związany z zapisem tego typu znaków. W ramach UTF-16 pozwala on na zapisanie wartości powyżej U+FFFF:
console.log(str.charCodeAt(0)); // 55362 console.log(str.charCodeAt(1)); // 57271
Metoda charCodeAt() zwraca 16-bitowy numer reprezentujący daną jednostkę znaku. W ramach standardu ECMAScript2015 otrzymujemy do dyspozycji nową metodę – codePointAt(), która zamiast poszczególnych jednostek (jak ma to miejsce w przypadku metody charCodeAt()) zwraca kody w ramach standardu Unicode UTF-8.
console.log(str.codePointAt(0)); // 134071 console.log(str.codePointAt(1)); // 57271 console.log(str.codePointAt(0) === 0x20BB7); // true
Metoda codePointAt() zwraca więc dokładnie takie same wartości, poza znakami BMP.
BMP — Basic Multilingual Plane — the first 2^16 code points.
Kolejna nowe metoda w ES6 związana z Unicode, to fromCodePoint(), czyli operacja odwrotna do funkcji codePointAt():
console.log(String.fromCodePoint(134071)); // "𠮷" console.log(String.fromCodePoint(0x20BB7)); // "𠮷"
Kody znaków w Unicode reprezentowane są przez 6 znaków, z czego dwa pierwsze to \u, a kolejne 4, to cyfry w zapisie szesnastkowym.
W przypadku UTF-16 sytuacja wygląda nieco inaczej, bo tego typu wartości reprezentowane są przez co najmniej 5 znaków i maksymalnie 10. Ich format to:
\u{1-6 liczb w zapisie szesnastkowym}
Spójrzmy zatem na przykłady z wykorzystaniem składni ES6 oraz porównanie ze składnią ES5:
// ES6 console.log('\u{20BB7}'); // 𠮷 console.log('\u{20BB7}' === '\\uD842\\uDFB7'); // true // ES5 console.log('\u20BB7); // 7! console.log('\u20BB7' === '\\uD842\\uDFB7'); // false
W przypadku, gdy będziemy próbowali dopasować jeden znak poprzez wyrażenie regularne, w ES5 otrzymamy nieoczekiwany rezultat:
console.log(/^.$/.test(str)); // false
str ma bowiem długość 2, a nie 1.
Dzięki ES6 mamy możliwość wykorzystać w tym wypadku nową flagę (u), która jest odpowiedzialna za poprawne interpretowanie znaków Unicode.
console.log(/^.$/u.test(str)); // true
pętla for-of i konstrukcja spread
Przy okazji łańcuchów znaków wspomnę także o nowym sposobie iteracji za pomocą metody for-of. Napiszę o niej więcej w kolejnym artykule, ale już teraz nadmienię, że pozwala ona na iterowanie po łańcuchach znaków z pełnym wsparciem dla Unicode w standardzie UTF-16:
let str = 'abc\\uD842\\uDFB7'; console.log(str.length); // 5 for (let c of str) { console.log(c); // a // b // c // 𠮷 }
Kolejna ciekawostka to fakt, że również konstrukcja spread pozwala nam bardzo prosto przetransformować łańcuch znaków do postaci tablicy i to z pełnym wsparciem dla UTF-16:
let str = 'abc\\uD842\\uDFB7'; let chars = […str]; console.log(chars); // ['a', 'b', 'c', '𠮷']
kolejne nowe metody
Na koniec zostawiłem sobie opis kilku nowych, ciekawych metod, dzięki którym możemy w prosty sposób operować na łańcuchach znaków.
repeat(n) – powtórzenie łańcucha znaków n razy
console.log('abc|'.repeat(3)); // 'abc|abc|abc|'
startsWith(str, starts = 0) : boolean – sprawdzenie, czy łańcuch znaków zaczyna się od str, zaczynając sprawdzanie od starts
console.log('ecmascript'.startsWith('ecma')); // true console.log('ecmascript'.startsWith('script', 4)); // true
endsWith(str, ends = str.length) : boolean – sprawdzenie, czy łańcuch znaków kończy się na str, gdzie parametr ends definiuje nam koniec
console.log('ecmascript'.endsWith('script')); // true console.log('ecmascript'.endsWith('ecma', 4)); // true
includes(str, starts = 0) : boolean – sprawdzenie, czy łańcuch znaków zawiera w sobie wartość str, zaczynając sprawdzanie od starts
onsole.log('ecmascript'.includes('ecma')); // true console.log('ecmascript'.includes('script', 4)); // true