Obiekt `String`
Ten artykuł wyjaśnia obiekt String.
Wyjaśnienie obejmuje wszystko od podstaw po zaawansowane techniki, w tym pułapki związane z Unicode i wyrażeniami regularnymi, krok po kroku i w łatwy do zrozumienia sposób.
YouTube Video
Obiekt String
Łańcuchy znaków w JavaScript są jednym z najczęściej używanych typów w codziennym programowaniu.
Różnica między prostymi łańcuchami znaków a obiektami String
Proste łańcuchy znaków (takie jak "hello") zachowują się inaczej niż obiekty otaczające, takie jak new String("hello"). Zazwyczaj należy używać typów prostych, a potrzeba użycia formy obiektowej jest znikoma.
1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b); // false — wrapper objects are not strictly equal
- Ten kod pokazuje różnicę typu między typem prostym a obiektem opakowującym oraz jak zachowują się przy ścisłym porównaniu. W większości przypadków unikaj używania
new String()i korzystaj z typów prostych.
Sposoby tworzenia łańcuchów znaków (literały i template literals)
Template literals są przydatne do wstawiania zmiennych i pisania wielowierszowych łańcuchów znaków. Możesz intuicyjnie wstawiać zmienne oraz oceniać wyrażenia.
1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
- Template literals są bardzo czytelne i idealne do budowania złożonych łańcuchów znaków, w tym wielowierszowych.
Typowe metody (wyszukiwanie i wyodrębnianie podłańcuchów)
Obiekt String posiada wiele podstawowych metod.
1const text = "Hello, world! Hello again.";
2
3// search
4console.log(text.indexOf("Hello")); // 0
5console.log(text.indexOf("Hello", 1)); // 13
6console.log(text.includes("world")); // true
7console.log(text.startsWith("Hello")); // true
8console.log(text.endsWith("again.")); // true
9
10// slice / substring
11console.log(text.slice(7, 12)); // "world"
12console.log(text.substring(7, 12)); // "world"
sliceisubstringsą podobne, ale inaczej obsługują ujemne indeksy.sliceinterpretuje wartości ujemne jako pozycje liczone od końca. Upewnij się, której z nich chcesz użyć.
Dzielenie i łączenie (split / join)
Często dzieli się łańcuch na tablicę do przetwarzania, a następnie łączy z powrotem.
1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
- Popularnym wzorcem jest użycie
splitdo podziału łańcucha, przetworzenie wynikowej tablicy za pomocąmaplubfilter, a następnie połączenie jej z powrotem za pomocąjoin.
Zastępowanie i wyrażenia regularne
replace zastępuje tylko pierwsze dopasowanie. Jeśli chcesz zastąpić wszystkie dopasowania, użyj flagi g z wyrażeniem regularnym. Przekazując funkcję jako zamiennik, możesz również wykonywać dynamiczne zamiany.
1const s = "foo 1 foo 2";
2
3// replace first only
4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
5
6// replace all using regex
7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
8
9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r); // "foo 10 foo 20"
- Korzystając z dynamicznej zamiany za pomocą funkcji, możesz zwięźle napisać kod, który analizuje i przekształca dopasowane fragmenty.
Zmiana wielkości liter i normalizacja
Dla obsługi wielu języków i porównywania, oprócz toLowerCase i toUpperCase, ważna jest także normalizacja Unicode (normalize). Jest to szczególnie konieczne przy porównywaniu znaków z akcentami.
1// Case conversion example:
2// "\u00DF" represents the German letter "ß".
3// In some locales, converting "ß" to uppercase becomes "SS".
4// JavaScript follows this behavior.
5console.log("\u00DF");
6console.log("\u00DF".toUpperCase()); // "SS"
7
8// Unicode normalization example:
9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b); // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
- Różne reprezentacje Unicode, takie jak ligatury i znaki łączone, nie będą równe w tej formie, dlatego należy użyć
normalize()przed porównaniem.
Unicode i punkty kodowe (obsługa par surrogatów)
Łańcuchy znaków w JavaScript są sekwencjami jednostek kodowych UTF-16, więc niektóre znaki, takie jak emotikony, mogą zajmować dwie jednostki kodowe dla jednego znaku. Aby obsłużyć rzeczywiste jednostki znakowe, użyj Array.from, operatora spread lub for...of.
1// Emoji composed with multiple code points:
2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩💻
4const s = "\u{1F469}\u{200D}\u{1F4BB}";
5console.log(s);
6
7// Length in UTF-16 code units (not actual Unicode characters):
8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14 console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));lengthzwraca liczbę jednostek kodowych, więc możesz nie otrzymać oczekiwanej liczby przy emotikonach lub ligaturach.for...ofiArray.fromobsługują coś zbliżonego do wyświetlanych znaków (klastrów grafemowych), ale jeśli potrzebujesz pełnej obsługi grafemów, rozważ użycie specjalistycznej biblioteki.
Bezpieczna zamiana z użyciem wyrażeń regularnych (przy obsłudze danych użytkownika)
Jeśli zapomnisz o ucieczce danych wejściowych użytkownika podczas osadzania ich w wyrażeniu regularnym, może to prowadzić do niespodziewanych zachowań i luk w zabezpieczeniach. Zawsze stosuj ucieczkę znaków danych wejściowych użytkownika przed użyciem ich we wzorcu.
1function escapeRegex(s) {
2 return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
- Nigdy nie używaj bezpośrednio ciągów znaków od użytkownika w wyrażeniach regularnych; zawsze je ucieczkuj przed zbudowaniem wyrażenia.
Porady dotyczące wydajności: konkatenacja i template strings
Łącząc wiele małych łańcuchów znaków w sekwencji, umieszczenie ich w tablicy i użycie join może być wydajniejsze. Z drugiej strony, template strings są bardzo czytelne i wystarczająco szybkie w większości przypadków.
1// concatenation in loop (less ideal)
2let s = "";
3for (let i = 0; i < 1000; i++) {
4 s += i + ",";
5}
6
7// using array + join (often faster for many pieces)
8const parts = [];
9for (let i = 0; i < 1000; i++) {
10 parts.push(i + ",");
11}
12const s2 = parts.join("");- Nowoczesne silniki JavaScript są bardzo zoptymalizowane, więc nie musisz się martwić o wydajność przy niewielkiej liczbie konkatenacji. Jednakże, jeśli musisz wykonać dziesiątki tysięcy konkatenacji, użycie
joinmoże być wydajniejsze.
Praktyczne przydatne techniki: padding, trim i repeat
trim, padStart, padEnd oraz repeat to wygodne metody, które są szczególnie przydatne w codziennym przetwarzaniu ciągów znaków. Często są używane w praktycznych sytuacjach, takich jak formatowanie wartości wejściowych lub standaryzacja formatów wyjściowych.
1console.log(" hello ".trim()); // "hello"
2console.log("5".padStart(3, "0")); // "005"
3console.log("x".repeat(5)); // "xxxxx"
- Metody te można wykorzystać do normalizacji danych wejściowych lub generowania danych o stałej szerokości.
Porównywanie łańcuchów znaków (porównanie z lokalizacją)
localeCompare jest skuteczne do porównywania łańcuchów znaków według kolejności słownikowej w różnych językach. Możesz określić język oraz opcje czułości (na przykład rozróżnianie wielkości znaków).
1console.log(
2 "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6 "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
- Do zlokalizowanych porównań używaj
localeComparei określ odpowiednią lokalizację oraz opcje.
Praktyczny przykład: konwersja wiersza CSV do obiektu (praktyczny przebieg)
Powszechnym przypadkiem użycia jest parsowanie pojedynczego wiersza CSV na obiekt przy użyciu połączenia split, trim i map. Dla pól w cudzysłowach lub złożonych plików CSV używaj dedykowanego parsera CSV.
1// simple CSV parse (no quotes handling)
2function parseCsvLine(line, headers) {
3 const values = line.split(",").map(v => v.trim());
4 const obj = {};
5 headers.forEach((h, i) => obj[h] = values[i] ?? null);
6 return obj;
7}
8
9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
- Ta metoda działa dla prostych plików CSV, ale pamiętaj, że nie poradzi sobie z przecinkiem wewnątrz pola w cudzysłowie.
Częste pułapki
Istnieją łatwe do przeoczenia specyfikacje i zachowania w obsłudze łańcuchów znaków w JavaScript. Aby uniknąć nieoczekiwanych błędów, ważne jest, aby pamiętać o poniższych kwestiach.
- Użycie
new String()może prowadzić do niepoprawnych wyników przy sprawdzaniu typu lub porównaniach. W większości przypadków wystarczają proste typy łańcuchów znaków. - W Unicode pojedynczy widoczny znak może się składać z wielu jednostek kodowych. Wartość zwracana przez
lengthmoże nie odpowiadać faktycznej liczbie wyświetlanych znaków. - Gdy wstawiasz dane użytkownika do wyrażenia regularnego, zawsze najpierw je ucieczkuj.
String.prototype.replace()domyślnie zastępuje tylko pierwsze dopasowanie. Jeśli chcesz zastąpić wszystkie wystąpienia, użyj flagi/gw swoim wyrażeniu regularnym.- Łańcuchy znaków są niezmienne, więc operacje zawsze zwracają nowy łańcuch. Ważne jest, aby zawsze przypisywać zwracaną wartość.
Podsumowanie
Chociaż łańcuchy znaków w JavaScript mogą wydawać się proste, ważne jest, aby rozumieć ich cechy związane z Unicode i niezmiennością. Opanowując podstawy, możesz znacznie poprawić niezawodność i czytelność przetwarzania łańcuchów znaków.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.