Объект `String`
В этой статье объясняется объект String.
Объяснение охватывает все — от основ до продвинутых приёмов, включая подводные камни, связанные с Unicode и регулярными выражениями, шаг за шагом и в понятной форме.
YouTube Video
Объект String
Строки в JavaScript являются одним из самых часто используемых типов в повседневной разработке.
Различие между строками-примитивами и объектами String
Строки-примитивы (например, "hello") ведут себя иначе, чем объекты-обёртки, такие как new String("hello"). Обычно следует использовать примитивы, и очень редко возникает необходимость применять объектную форму.
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
- Этот код показывает разницу в типах между примитивом и обёрткой, а также то, как они ведут себя при строгом сравнении. В большинстве случаев избегайте использования
new String()и используйте примитивные строки.
Способы создания строк (литералы и шаблонные строки)
Шаблонные строки удобны для вставки переменных и написания многострочных строк. Вы можете интуитивно вставлять переменные и вычислять выражения.
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"
- Шаблонные строки очень удобочитаемы и идеально подходят для создания сложных, в том числе многострочных, строк.
Основные методы (поиск и выделение подстроки)
Объект String содержит множество основных методов.
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"
sliceиsubstringпохожи, но по-разному обрабатывают отрицательные индексы.sliceинтерпретирует отрицательные значения как позиции с конца строки. Важно точно знать, какой из них использовать.
Разделение и объединение (split / join)
Часто строку разбивают на массив для обработки, а затем снова объединяют.
1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
- Обычно используют следующий паттерн: разделяют строку с помощью
split, обрабатывают полученный массив с помощьюmapилиfilter, а затем соединяют обратно черезjoin.
Замена и регулярные выражения
replace заменяет только первое совпадение. Если вы хотите заменить все совпадения, используйте флаг g с регулярным выражением. Передавая функцию как аргумент замены, вы также можете выполнять динамическую замену.
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"
- С помощью динамической замены через функцию вы можете лаконично писать код для анализа и преобразования совпадений.
Изменение регистра и нормализация
Для поддержки и сравнения на разных языках, помимо toLowerCase и toUpperCase, также важна нормализация Unicode (normalize). Это особенно необходимо при сравнении символов с диакритическими знаками.
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
- Различные представления Unicode, такие как лигатуры и составные символы, не будут равны как есть, поэтому используйте
normalize()перед сравнением.
Unicode и кодовые точки (обработка суррогатных пар)
Строки в JavaScript представляют собой последовательность единиц кода UTF-16, поэтому некоторые символы, например эмодзи, могут занимать две единицы кода на один символ. Чтобы работать с реальными символами, используйте Array.from, оператор расширения (...) или цикл 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));lengthвозвращает количество кодовых единиц, поэтому вы можете получить неожиданный результат с эмодзи или лигатурами.for...ofиArray.fromработают почти с отображаемыми символами (графемными кластерами), но для полной поддержки графем рекомендуется использовать специализированную библиотеку.
Безопасная замена по регулярному выражению (при обработке пользовательского ввода)
Если вы забудете экранировать пользовательский ввод при вставке его в регулярное выражение, это может привести к неожиданному поведению и уязвимостям. Всегда экранируйте пользовательский ввод перед использованием его в шаблоне.
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"
- Никогда не используйте пользовательские строки напрямую в регулярных выражениях; всегда экранируйте их перед созданием regex.
Советы по производительности: конкатенация и шаблоны
Если требуется объединить много маленьких строк, эффективнее сначала поместить их в массив, а затем объединить с помощью join. С другой стороны, шаблонные строки очень удобочитаемы и обычно достаточно быстры.
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("");- Современные движки JavaScript хорошо оптимизированы, поэтому при небольшом числе объединений о производительности можно не беспокоиться. Однако при необходимости объединять строки десятки тысяч раз, использование
joinбудет эффективнее.
Практические приёмы: заполнение, обрезка и повторение
trim, padStart, padEnd и repeat — это удобные методы, которые особенно полезны в повседневной обработке строк. Они часто используются на практике, например, при форматировании входных данных или стандартизации выходных форматов.
1console.log(" hello ".trim()); // "hello"
2console.log("5".padStart(3, "0")); // "005"
3console.log("x".repeat(5)); // "xxxxx"
- Эти методы можно использовать для нормализации вводимых данных или генерации вывода фиксированной ширины.
Сравнение строк (с учётом локали)
localeCompare эффективен для сравнения строк по словарному порядку для разных языков. Можно указать язык и параметры чувствительности (например, чувствительность к регистру).
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
- Для интернационализированного сравнения используйте
localeCompareс указанием подходящей локали и параметров.
Практический пример: преобразование строки CSV в объект (практический процесс)
Обычный случай — разбор одной строки CSV в объект с помощью комбинации split, trim и map. Для полей в кавычках и сложных CSV-файлов используйте специализированный анализатор 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" }
- Этот способ работает для простого CSV, но учтите: если запятая стоит внутри кавычек, он не справится.
Распространённые ошибки
В работе со строками в JavaScript есть некоторые нюансы и особенности, которые легко упустить из виду. Чтобы избежать неожиданных ошибок, важно помнить о следующих моментах.
- Использование
new String()может привести к неверным результатам при проверке типов или сравнении. В большинстве случаев достаточно примитивных строк. - В Unicode один видимый символ может состоять из нескольких кодовых единиц. Поэтому значение, возвращаемое функцией
length, может не соответствовать реальному количеству отображаемых символов. - При использовании пользовательского ввода в регулярном выражении всегда предварительно экранируйте его.
- По умолчанию
String.prototype.replace()заменяет только первое совпадение. Если вы хотите заменить все вхождения, используйте флаг/gв регулярном выражении. - Строки неизменяемы, поэтому операции всегда возвращают новую строку. Важно всегда присваивать возвращаемое значение.
Резюме
Хотя строки в JavaScript могут показаться простыми, важно понимать их особенности, связанные с Unicode и неизменяемостью. Освоив основы, вы сможете значительно повысить надёжность и читаемость работы со строками.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.