6. Ciągi znaków
Ciągi znaków w JavaScript są bardzo podobne do tych w PHP.

6.1. skrypt [str-01]
Pierwszą rzeczą, którą należy zrozumieć, jest to, że po utworzeniu ciąg znaków nie można go już modyfikować. Istnieje wiele metod tworzenia nowego ciągu znaków na podstawie ciągu początkowego, ale ten ostatni pozostaje zawsze niezmieniony. Ponadto ciąg znaków może występować w dwóch typach:
- [string], gdy jest inicjowany za pomocą literału łańcuchowego;
- [object], gdy jest tworzony jako instancja klasy [String];
'use strict';
// ciągi znaków są tylko do odczytu (nie można ich modyfikować)
// ciąg znaków
const chaîne1 = "abcd ";
// typ
console.log("typeof(chaîne1)=", typeof (chaîne1));
// znak nr 2
console.log("chaîne1[2]=", chaîne1[2]);
// powoduje błąd
chaîne1[2] = "0";
Wykonanie
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Temp\19-09-01\javascript\strings\str-01.js"
typeof(chaîne1)= string
chaîne1[2]= c
c:\Temp\19-09-01\javascript\strings\str-01.js:1
TypeError: Cannot assign to read only property '2' of string 'abcd '
at Object.<anonymous> (c:\Temp\19-09-01\javascript\strings\str-01.js:12:12)
at Generator.next (<anonymous>)
6.2. skrypt [str-02]
Ten skrypt pokazuje, że ciąg znaków można utworzyć na dwa sposoby.
'„use strict”;
// ciągi znaków mogą być dwóch typów
// ciąg literałowy
const chaîne1 = "abcd ";
// typ
console.log("typeof(chaîne1)=", typeof (chaîne1));
// instancja typu String
const chaîne2 = new String("xyzt");
// typ
console.log("typeof(chaîne2)=", typeof (chaîne2));
// inna notacja (bez new) – typ [string], a nie [object]
const chaîne3 = String("12 34");
// typ
console.log("typeof(chaîne3)=", typeof (chaîne3));
// typ [string] i typ [object] oferują te same metody, co klasa String
console.log("chaîne1.length=", chaîne1.length);
console.log("chaîne2.length=", chaîne2.length);
Komentarze
- wiersz 6: typowa metoda definiowania ciągu znaków. [chaîne1] będzie typu [string];
- wiersz 10: ciąg znaków można utworzyć za pomocą konstruktora klasy [String]. [chaîne2] będzie typu [object];
Wykonanie
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe "c:\Temp\19-09-01\javascript\strings\str-02.js"
typeof(chaîne1)= string
typeof(chaîne2)= object
typeof(chaîne3)= string
chaîne1.length= 5
chaîne2.length= 4
Typ [string] korzysta z metod klasy [String].
6.3. Skrypt [str-03]
Ten skrypt przedstawia konkretny ciąg znaków z interpolacją zmiennych.
'use strict';
// ciąg znaków
const chaîne = "Introduction à Javascript par l'exemple";
// ciąg znaków z interpolacją zmiennych
const str = `[${chaîne}].substr(3, 2)=` + chaîne.substr(3, 2)
console.log(str);
Komentarze
- wiersz 6: możliwe jest stosowanie ciągów znaków zawierających wyrażenia ${zmienna}, które są zastępowane wartością zmiennej. Działa to na tej samej zasadzie, co zmienne $ w ciągach znaków PHP. Należy zwrócić uwagę na zapis takiego ciągu: jest on otoczony znakami „backstick” lub odwrotnego apostrofu (AltGr-7 na klawiaturze francuskiej);
Wykonanie
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Temp\19-09-01\javascript\strings\tempCodeRunnerFile.js"
[Introduction à Javascript par l'exemple].substr(3, 2)=ro
6.4. skrypt [str-04]
Ciąg znaków z interpolacją zmiennych nadal nie jest wystarczający. Nie da się bowiem zastąpić zmiennej w wyrażeniu ${zmienna} żadnym wyrażeniem. Dla osób, które programowały w języku C, nie ma nic lepszego niż funkcje typu [printf, sprintf] do pisania lub tworzenia sformatowanych ciągów znaków. Setki programistów stworzyło tysiące pakietów JavaScript, tworząc ogromny ekosystem. Gdy pojawia się potrzeba, której nie zaspokaja natywnie JavaScript, nadchodzi czas, by poszukać pakietu, który ją zaspokoi. W tym celu korzystamy z menedżera pakietów [npm]. Posiada on opcję [search], która pozwala wyszukiwać ciąg znaków w opisach pakietów. [npm] zwraca listę pakietów spełniających kryteria wyszukiwania. Wyszukamy więc ciąg znaków [sprintf] w opisach pakietów:

- w kolumnie [4] – słowa kluczowe pakietów z kolumny [3];
- w kolumnie [5] – opisy pakietów z kolumny [3];
Kolejnym krokiem jest przejście na stronę narzędzia [npm], [https://www.npmjs.com/] i zapoznanie się z opisem pakietów:

W narzędziu [3] przeglądamy listę pakietów i wybieramy jeden z nich.

W opisie pakietu znajdują się informacje dotyczące jego instalacji i użytkowania:

Instalujemy pakiet [sprintf-js] w terminalu [VSCode]:

Ta instalacja zmodyfikuje plik [package.json] znajdujący się w katalogu głównym folderu [javascript] [2]:

Jak widać powyżej, pakiet został zainstalowany w katalogu [dependencies], czyli w pakietach niezbędnych do uruchomienia projektu. Przypominamy, że pakiety potrzebne wyłącznie podczas tworzenia projektu umieszcza się w katalogu [devDependencies]. Nie są one wykorzystywane podczas działania projektu. Ta różnica ma znaczenie przy tworzeniu ostatecznej wersji projektu przeznaczonej do wdrożenia produkcyjnego. Istnieją narzędzia służące do:
- zgromadzenie wszystkich plików jS niezbędnych do działania w jednym pliku. Pakiety z [devDependencies] nie są zatem uwzględnione w tym pliku końcowym;
- zminimalizować ten plik, tj. zmniejszyć jego rozmiar do minimum. W tym celu usuwa się na przykład wszystkie komentarze;
- „zaciemnienie” kodu, aby utrudnić jego zrozumienie. Na przykład zmienne „stawka”, „wynagrodzenie” i „podatek” zostaną zastąpione zmiennymi „a”, „b” i „c”;
- przeprowadzić inne optymalizacje;
Ta optymalizacja ostatecznego pliku projektu jS jest stosowana w programowaniu stron internetowych. Aplikacja internetowa może opierać się na bardzo wielu plikach JavaScript. Ich ładowanie przez przeglądarkę może spowolnić wyświetlanie pierwszej strony aplikacji. Powyższa optymalizacja ma na celu skrócenie tego czasu ładowania. Jeśli użytkownicy uznają czas ładowania za zbyt długi, aplikacja nie będzie z niej korzystać.
Teraz, gdy dysponujemy pakietem [sprintf-js], musimy z niego skorzystać. Jest to skrypt [str-04]:
'use strict';
// wykorzystanie zewnętrznego pakietu w celu uzyskania dostępu do funkcji sprintf
import { sprintf } from 'sprintf-js';
// ciąg znaków
const chaîne = "Introduction à Javascript par l'exemple";
// metoda
console.log(sprintf("[%s].substr(3,2)=[%s]", chaîne, chaîne.substr(3, 2)));
W przypadku ECMAScript 6 używamy słowa kluczowego [import] do zaimportowania obiektu wyeksportowanego przez pakiet. Aby dowiedzieć się, co eksportuje pakiet, można sprawdzić jego kod:

- w [1], kliknij prawym przyciskiem myszy na zaimportowany pakiet;
- w przypadku [2] chcemy wyświetlić jego definicję;
- w [3-4] widać, że pakiet eksportuje funkcję o nazwie [sprintf];
Funkcja [sprintf] z pakietu [sprintf-js] jest importowana za pomocą instrukcji:
import { sprintf } from 'sprintf-js';
Pełny kod:
'use strict';
// wykorzystanie zewnętrznego pakietu w celu uzyskania dostępu do funkcji sprintf
import { sprintf } from 'sprintf-js';
// ciąg
const chaîne = "Introduction à Javascript par l'exemple";
// metoda
console.log(sprintf("[%s].substr(3,2)=[%s]", chaîne, chaîne.substr(3, 2)));
daje następujące wyniki:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe "c:\Temp\19-09-01\javascript\strings\str-04.js"
c:\Temp\19-09-01\javascript\strings\str-04.js:3
import { sprintf } from 'sprintf-js';
^
SyntaxError: Unexpected token {
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
W wierszu 3 nie zrozumiano instrukcji [import]. Wynika to z faktu, że wersja 10.15.1 programu [node.js] wykorzystywana w ramach tego kursu (wrzesień 2019 r.) nie jest jeszcze zgodna ze standardem ECMAScript dotyczącym importowania pakietów zwanych modułami. W 2019 r. [node.js] jest zgodny ze standardem modułów o nazwie CommonJS. Włączenie modułów ECMAScript do [node.js] jest planowane na 2020 r. Również w tym przypadku programiści zabrali się do pracy i stworzyli pakiety umożliwiające korzystanie z modułów ES6 wraz z [node.js] już teraz (w 2019 r.).
Wykorzystamy pakiet o nazwie [esm] (ECMAScript Modules). Instalujemy go na terminalu projektu [javascript]:

W [4] widać, że instalacja pakietu [esm] [1-3] zmieniła plik [javascript/package.json].
To jeszcze nie koniec. Aby moduł [esm] był wykorzystywany przez [node.js], należy uruchomić ten ostatni z argumentem [-r esm].
Modyfikujemy zatem konfigurację rozszerzenia [Code Runner] w [VSCode]:


W [11] dodajemy argument [-r esm] i zapisujemy (Ctrl-S) konfigurację.
Teraz możemy uruchomić skrypt [str-04]:
'use strict';
// wykorzystanie zewnętrznego pakietu w celu uzyskania dostępu do funkcji sprintf
import { sprintf } from 'sprintf-js';
// ciąg
const chaîne = "Introduction à Javascript par l'exemple";
// metoda substr
console.log(sprintf("[%s].substr(3,2)=[%s]", chaîne, chaîne.substr(3, 2)));

6.5. skrypt [str-05]
Oto, co mówi dokumentacja na temat funkcji [sprintf]:
Symbole zastępcze w ciągu formatującym są oznaczone znakiem % i następuje po nich jeden lub więcej z poniższych elementów, w podanej kolejności:
- Opcjonalna liczba, po której następuje znak $, określająca, który indeks argumentu ma zostać użyty dla danej wartości. Jeśli nie zostanie to określone, argumenty zostaną umieszczone w tej samej kolejności, co symbole zastępcze w ciągu wejściowym.
- Opcjonalny znak +, który wymusza poprzedzenie wyniku znakiem plus lub minus w przypadku wartości liczbowych. Domyślnie w przypadku liczb ujemnych używany jest tylko znak -.
- Opcjonalny specyfikator wypełnienia, który określa, jakiego znaku użyć do wypełnienia (jeśli zostanie podany). Możliwe wartości to 0 lub dowolny inny znak poprzedzony znakiem ' (pojedynczy cudzysłów). Domyślnie wypełnieniem są spacje.
- Opcjonalny znak „-”, który powoduje, że funkcja sprintf wyrównuje wynik tego symbolu zastępczego do lewej strony. Domyślnie wynik jest wyrównany do prawej strony.
- Opcjonalna liczba określająca, ile znaków powinien mieć wynik. Jeśli wartość, która ma zostać zwrócona, jest krótsza od tej liczby, wynik zostanie uzupełniony. W połączeniu ze specyfikatorem typu j (JSON) długość uzupełnienia określa rozmiar tabulacji używany do wcięcia.
- Opcjonalny modyfikator precyzji, składający się z kropki (.) i następującej po niej liczby, określający liczbę cyfr, które powinny być wyświetlane w przypadku liczb zmiennoprzecinkowych. W połączeniu ze specyfikatorem typu g określa on liczbę cyfr znaczących. W przypadku zastosowania do ciągu znaków powoduje on obcięcie wyniku.
- Specyfikator typu, który może przybierać jedną z następujących postaci:
- % — zwraca literał znaku %
- b — zwraca liczbę całkowitą w postaci binarnej
- c — zwraca liczbę całkowitą jako znak o tej wartości
- d lub i — zwraca liczbę całkowitą jako liczbę dziesiętną ze znakiem
- e — zwraca liczbę zmiennoprzecinkową w notacji naukowej
- u — zwraca liczbę całkowitą jako liczbę dziesiętną bez znaku
- f — zwraca liczbę zmiennoprzecinkową bez zmian; zob. uwagi dotyczące precyzji powyżej
- g — zwraca liczbę zmiennoprzecinkową bez zmian; patrz uwagi dotyczące precyzji powyżej
- o — zwraca liczbę całkowitą w postaci liczby ósemkowej
- s — zwraca ciąg znaków bez zmian
- t — zwraca wartość true lub false
- T — zwraca typ argumentu1
- v — zwraca wartość pierwotną podanego argumentu
- x — zwraca liczbę całkowitą w postaci szesnastkowej (małymi literami)
- X — zwraca liczbę całkowitą w postaci liczby szesnastkowej (wielkimi literami)
- j — zwraca obiekt lub tablicę typu JavaScript jako ciąg znaków zakodowany w formacie JSON
Skrypt [script-05] wykorzystuje niektóre z tych formatów:
'use strict';
// wykorzystanie zewnętrznego pakietu w celu uzyskania dostępu do funkcji sprintf
import { sprintf } from 'sprintf-js';
// ciąg znaków
const chaîne = "Javascript";
// ciągi znaków
console.log(sprintf("[%s, %%s]=>[%s]", chaîne, chaîne));
console.log(sprintf("[%s, %%20s]=>[%20s]", chaîne, chaîne));
console.log(sprintf("[%s, %%-20s]=>[%-20s]", chaîne, chaîne));
// liczby całkowite
console.log(sprintf("[%d, %%d]=>[%d]", 10, 10));
console.log(sprintf("[%d, %%4d]=>[%4d]", 10, 10));
console.log(sprintf("[%d, %%-4d]=>[%-4d]", 10, 10));
console.log(sprintf("[%d, %%04d]=>[%04d]", 10, 10));
// liczby zmiennoprzecinkowe
console.log(sprintf("[%f, %%f]=>[%f]", -10.5, -10.5));
console.log(sprintf("[%f, %%10.2f]=>[%10.2f]", -10.5, -10.5));
console.log(sprintf("[%f, %%-10.2f]=>[%-10.2f]", -10.5, -10.5));
console.log(sprintf("[%f, %%010.3f]=>[%010.3f]", -10.5, -10.5));
// json
console.log(sprintf("personne (%%j)=%j", { nom: "mathieu", âge: 34 }));
// typ
console.log(sprintf("type personne (%%T)=%T", { nom: "mathieu", âge: 34 }));
// wartość logiczna
console.log(sprintf("booléen (%%t)=%t", 4 === 4));
Wykonanie
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\strings\str-05.js"
[Javascript, %s]=>[Javascript]
[Javascript, %20s]=>[ Javascript]
[Javascript, %-20s]=>[Javascript ]
[10, %d]=>[10]
[10, %4d]=>[ 10]
[10, %-4d]=>[10 ]
[10, %04d]=>[0010]
[-10.5, %f]=>[-10.5]
[-10.5, %10.2f]=>[ -10.50]
[-10.5, %-10.2f]=>[-10.50 ]
[-10.5, %010.3f]=>[-00010.500]
personne (%j)={"nom":"mathieu","âge":34}
type personne (%T)=object
booléen (%t)=true
6.6. skrypt [str-06]
Skrypt [str-06] przedstawia kilka metod klasy [String], które można również zastosować w przypadku typu [string]:
'use strict';
// wykorzystanie zewnętrznego pakietu w celu uzyskania dostępu do funkcji sprintf
import { sprintf } from 'sprintf-js';
// ciąg znaków
const chaîne = " Introduction à Javascript ";
// kilka metod
// substr(10,2): 2 znaki, począwszy od 10.
console.log(sprintf("[%s].substr(10,2)=[%s]", chaîne, chaîne.substr(10, 2)));
// trim: usuwanie spacji na początku i na końcu ciągu znaków (spacja = \b \t \r \n \f)
console.log(sprintf("[%s].trim()=[%s]", chaîne, chaîne.trim()));
// toLowerCase: zamiana na małe litery
console.log(sprintf("[%s].toLowerCase=[%s]", chaîne, chaîne.toLowerCase()));
// toUpperCase: zamiana na wielkie litery
console.log(sprintf("[%s].toUpperCase=[%s]", chaîne, chaîne.toUpperCase()));
// indexOf: pozycja poszukiwanego c iągu w ciągu, -1, jeśli podciąg nie istnieje
console.log(sprintf("[%s].indexOf('Java')=[%s]", chaîne, chaîne.indexOf('Java')));
console.log(sprintf("[%s].trim().indexOf('abcd')=[%s]", chaîne, chaîne.indexOf('abcd')));
// includes: true, jeśli szukany ciąg występuje w ciągu
console.log(sprintf("[%s].includes('Java')=[%s]", chaîne, chaîne.includes('Java')));
// length: długość ciągu – nie jest to metoda, lecz właściwość
console.log(sprintf("[%s].length=[%s]", chaîne, chaîne.length));
// slice (7,10): podciąg znaków od 7 do 9
console.log(sprintf("[%s].slice(7,10)=[%s]", chaîne, chaîne.slice(7, 10)));
// match: wyszukuje wyrażenie w ciągu znaków – wyrażenie to może być wyrażeniem regularnym
// /intro/i: wyrażenie regularne wskazujące ciąg znaków [intro], zarówno w wielkich, jak i małych literach
// zwraca znaleziony ciąg znaków
console.log(sprintf("[%s].match(/intro/i)=[%s]", chaîne, chaîne.match(/intro/i)));
// replace: zastępuje ciąg1 ciągiem2 w ciągu
// zastępuje pierwsze wystąpienie znaku „i” znakiem „x”
console.log(sprintf("[%s].replace('i','x')=[%s]", chaîne, chaîne.replace('i', 'x')));
// zastępuje wszystkie wystąpienia znaku „i” znakiem „x”
// /i/g to wyrażenie regularne oznaczające wszystkie (g) wystąpienia znaku i
console.log(sprintf("[%s].replace(/i/g,'x')=[%s]", chaîne, chaîne.replace(/i/g, 'x')));
// split: dzieli ciąg znaków na słowa oddzielone parametrem split
// zwraca tablicę tych słów
// /\s*/: słowa oddzielone zerem lub większą liczbą spacji
console.log(sprintf("[%s].split(/\\s*/)=[%s]", chaîne, chaîne.split(/\s*/)));
// /\s+/: słowa oddzielone jedną lub kilkoma spacjami
console.log(sprintf("[%s].split(/\\s+/)=[%s]", chaîne, chaîne.split(/\s+/)));
Wykonanie
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\strings\str-06.js"
[ Introduction à Javascript ].substr(10,2)=[ti]
[ Introduction à Javascript ].trim()=[Introduction à Javascript]
[ Introduction à Javascript ].toLowerCase=[ introduction à javascript ]
[ Introduction à Javascript ].toUpperCase=[ INTRODUCTION À JAVASCRIPT ]
[ Introduction à Javascript ].indexOf('Java')=[17]
[ Introduction à Javascript ].trim().indexOf('abcd')=[-1]
[ Introduction à Javascript ].includes('Java')=[true]
[ Introduction à Javascript ].length=[28]
[ Introduction à Javascript ].slice(7,10)=[duc]
[ Introduction à Javascript ].match(/intro/i)=[Intro]
[ Introduction à Javascript ].replace('i','x')=[ Introductxon à Javascript ]
[ Introduction à Javascript ].replace(/i/g,'x')=[ Introductxon à Javascrxpt ]
[ Introduction à Javascript ].split(/\s*/)=[,I,n,t,r,o,d,u,c,t,i,o,n,à,J,a,v,a,s,c,r,i,p,t,]
[ Introduction à Javascript ].split(/\s+/)=[,Introduction,à,Javascript,]