ES6 – strings

javascript

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