📘 TypeScript

String Methods — Complete Guide in TypeScript: Detailed Guide

Что это такое и зачем? (аналогия из реальной жизни)

Представьте, что вы работаете в библиотеке. У вас есть книга — это строка (текст). Но просто иметь книгу мало. Вам нужно уметь с ней работать: найти нужную главу, заменить устаревшие слова, вырезать цитату, проверить, начинается ли она с заглавной буквы, или посчитать, сколько в ней страниц.

В программировании методы строк — это те самые инструменты, которые позволяют вам манипулировать текстом. Строка — это не просто набор букв, это объект, у которого есть встроенные функции (методы). Они умеют:

  • Искать подстроку (как найти главу в книге)
  • Заменять части текста (как заменить устаревшее слово на новое)
  • Вырезать часть строки (как сделать вырезку из газеты)
  • Проверять, соответствует ли строка шаблону (как проверить, является ли строка email-адресом)
  • Изменять регистр (сделать все буквы заглавными или строчными)
  • Удалять лишние пробелы (как подстричь газон)

Зачем это нужно? Представьте, что вы пишете программу для регистрации пользователей. Вам нужно:

  1. Убрать лишние пробелы из имени (" Иван " → "Иван")
  2. Проверить, что email содержит символ @
  3. Сделать первую букву фамилии заглавной
  4. Заменить в тексте все плохие слова на звёздочки

Без методов строк вам пришлось бы писать сложные циклы и проверки вручную. С методами — это одна-две строчки кода.

Важно: Строки в TypeScript (и JavaScript) — это неизменяемые (immutable) типы данных. Это значит, что любой метод строки не меняет исходную строку, а создаёт новую. Представьте, что вы делаете ксерокопию страницы и работаете с копией — оригинал остаётся нетронутым.

Теория: как это работает изнутри

Чтобы понять, как работают методы строк, нужно заглянуть "под капот" TypeScript.

Строка как массив символов

Внутри компьютера строка хранится как упорядоченная последовательность символов (символ — это буква, цифра, знак препинания или пробел). У каждого символа есть свой индекс — номер позиции, начиная с нуля.

Например, строка "Привет" выглядит так:

Индексы:  0    1    2    3    4    5
Символы:  П    р    и    в    е    т

Большинство методов строк используют эту нумерацию. Когда вы говорите "найди символ на позиции 3", компьютер мгновенно обращается к ячейке памяти с этим индексом.

Unicode и кодировка

Современные строки в TypeScript хранятся в кодировке UTF-16. Это значит, что каждый символ может занимать 2 или 4 байта. Для обычных букв (латиница, кириллица) — 2 байта. Для редких символов (например, эмодзи 🎉) — 4 байта.

Это важно знать, потому что некоторые методы (например, charAt) работают с 2-байтовыми единицами, и эмодзи могут "сломаться". Для работы с эмодзи лучше использовать современные методы (ES6+), которые понимают полные символы.

Примитив vs Объект

В TypeScript строки могут быть двух видов:

  • Примитив (string): let name = "Анна"; — простое значение.
  • Объект (String): let name = new String("Анна"); — полноценный объект с методами.

На практике вы почти всегда будете использовать примитивы. Когда вы вызываете метод на примитиве, TypeScript временно "оборачивает" его в объект, выполняет метод и возвращает результат. Это называется автоупаковка (autoboxing).

Совет: Никогда не используйте new String(). Это приводит к путанице, потому что typeof new String("текст") вернёт "object", а не "string". Всегда используйте строковые литералы: "текст" или 'текст'.

Методы — это функции, привязанные к строке

Каждый метод — это функция, которая "живёт" внутри прототипа строки (String.prototype). Когда вы пишете "hello".toUpperCase(), TypeScript:

  1. Берёт строку "hello"
  2. Ищет метод toUpperCase в прототипе
  3. Выполняет его, создавая новую строку "HELLO"
  4. Возвращает новую строку (оригинал не меняется)

Синтаксис в TypeScript: все формы и варианты

В TypeScript методы строк вызываются через точку после строки (или переменной, содержащей строку). Общий синтаксис:

строка.имяМетода(аргументы)

Где:

  • строка — это либо строковый литерал ("текст"), либо переменная (let s = "текст")
  • имяМетода — одно из стандартных имён (например, toUpperCase, slice)
  • аргументы — параметры, которые принимает метод (могут быть обязательными или опциональными)

Полный справочник методов (таблица)

Метод Описание Возвращает Пример
charAt(index) Возвращает символ по указанному индексу Строка (1 символ) "Привет".charAt(0) → "П"
charCodeAt(index) Возвращает Unicode-код символа Число "A".charCodeAt(0) → 65
concat(str1, str2, ...) Объединяет строки (лучше использовать + или шаблонные строки) Новая строка "Hello".concat(" ", "World") → "Hello World"
includes(searchString, position?) Проверяет, содержит ли строка подстроку Boolean "Hello".includes("ell") → true
endsWith(searchString, length?) Проверяет, заканчивается ли строка на подстроку Boolean "Hello".endsWith("lo") → true
indexOf(searchString, position?) Возвращает индекс первого вхождения подстроки (или -1) Число "Hello".indexOf("l") → 2
lastIndexOf(searchString, position?) Возвращает индекс последнего вхождения подстроки Число "Hello".lastIndexOf("l") → 3
localeCompare(compareString) Сравнивает строки с учётом локали (для сортировки) Число (-1, 0, 1) "а".localeCompare("б") → -1
match(regexp) Ищет совпадения с регулярным выражением Массив или null "a1b2".match(/d+/) → ["1"]
matchAll(regexp) Ищет все совпадения с регулярным выражением (ES2020) Итератор [... "a1b2".matchAll(/d/g)] → ["1", "2"]
normalize(form?) Приводит строку к нормализованной форме Unicode Новая строка "é".normalize() → "é"
padEnd(targetLength, padString?) Дополняет строку справа до указанной длины Новая строка "5".padEnd(3, "0") → "500"
padStart(targetLength, padString?) Дополняет строку слева до указанной длины Новая строка "5".padStart(3, "0") → "005"
repeat(count) Повторяет строку указанное количество раз Новая строка "Ha".repeat(3) → "HaHaHa"
replace(searchValue, replaceValue) Заменяет первое вхождение подстроки Новая строка "cat".replace("c", "b") → "bat"
replaceAll(searchValue, replaceValue) Заменяет все вхождения подстроки (ES2021) Новая строка "cat cat".replaceAll("cat", "dog") → "dog dog"
search(regexp) Ищет совпадение с регулярным выражением, возвращает индекс Число "abc123".search(/d/) → 3
slice(beginIndex, endIndex?) Извлекает часть строки (от begin до end, не включая end) Новая строка "Hello".slice(1, 4) → "ell"
split(separator, limit?) Разбивает строку на массив по разделителю Массив строк "a,b,c".split(",") → ["a", "b", "c"]
startsWith(searchString, position?) Проверяет, начинается ли строка с подстроки Boolean "Hello".startsWith("He") → true
substring(indexStart, indexEnd?) Извлекает часть строки (похож на slice, но с особенностями) Новая строка "Hello".substring(1, 4) → "ell"
toLocaleLowerCase(locale?) Переводит в нижний регистр с учётом локали Новая строка "İ".toLocaleLowerCase("tr") → "i"
toLocaleUpperCase(locale?) Переводит в верхний регистр с учётом локали Новая строка "i".toLocaleUpperCase("tr") → "İ"
toLowerCase() Переводит все символы в нижний регистр Новая строка "Hello".toLowerCase() → "hello"
toString() Возвращает строковое представление объекта Строка (123).toString() → "123"
toUpperCase() Переводит все символы в верхний регистр Новая строка "Hello".toUpperCase() → "HELLO"
trim() Удаляет пробелы с обоих концов строки Новая строка " Hi ".trim() → "Hi"
trimEnd() / trimRight() Удаляет пробелы в конце строки Новая строка "Hi ".trimEnd() → "Hi"
trimStart() / trimLeft() Удаляет пробелы в начале строки Новая строка " Hi".trimStart() → "Hi"
valueOf() Возвращает примитивное значение строки Строка new String("x").valueOf() → "x"
[Symbol.iterator] Позволяет перебирать строку по символам (через for...of) Итератор for (let ch of "Hi") { ... }
Примечание: Методы substring и slice очень похожи, но есть важное отличие: substring меняет местами аргументы, если первый больше второго, а slice возвращает пустую строку. Также slice поддерживает отрицательные индексы (отсчёт с конца), а substring — нет. Рекомендуется всегда использовать slice.

Примеры кода — от простого к сложному

Пример 1: Базовые методы (регистр, обрезка, поиск)

// Объявляем исходную строку
let message: string = "  Привет, Мир! JavaScript — это весело.  ";

// 1. toUpperCase() — переводим всё в верхний регистр
let upperMessage: string = message.toUpperCase();
console.log(upperMessage); // "  ПРИВЕТ, МИР! JAVASCRIPT — ЭТО ВЕСЕЛО.  "

// 2. toLowerCase() — переводим всё в нижний регистр
let lowerMessage: string = message.toLowerCase();
console.log(lowerMessage); // "  привет, мир! javascript — это весело.  "

// 3. trim() — удаляем пробелы в начале и конце
let trimmedMessage: string = message.trim();
console.log(trimmedMessage); // "Привет, Мир! JavaScript — это весело."

// 4. includes() — проверяем, содержит ли строка подстроку
let hasJavaScript: boolean = message.includes("JavaScript");
console.log(`Содержит "JavaScript"? ${hasJavaScript}`); // true

// 5. indexOf() — находим индекс первого вхождения
let indexOfComma: number = message.indexOf(",");
console.log(`Индекс запятой: ${indexOfComma}`); // 9 (с учётом пробелов в начале)

// 6. charAt() — получаем символ по индексу
let firstChar: string = message.charAt(2); // 2, потому что первые два символа — пробелы
console.log(`Третий символ (с учётом пробелов): "${firstChar}"`); // "П"

Объяснение: В этом примере мы видим, как работают самые популярные методы. Обратите внимание, что trim() не меняет исходную строку — он возвращает новую. Метод includes() очень удобен для проверки наличия подстроки без необходимости вычислять индекс и сравнивать с -1.

Пример 2: Извлечение частей строки (slice, substring, split)

let url: string = "https://example.com/users/12345";

// 1. slice() — извлекаем часть строки
// Берём символы с 8-го по 19-й (не включая 19)
let domain: string = url.slice(8, 19);
console.log(`Домен: ${domain}`); // "example.com"

// Отрицательные индексы — отсчёт с конца
let lastPart: string = url.slice(-5);
console.log(`Последние 5 символов: ${lastPart}`); // "12345"

// 2. substring() — похож на slice, но не поддерживает отрицательные индексы
let domainSubstring: string = url.substring(8, 19);
console.log(`Домен через substring: ${domainSubstring}`); // "example.com"

// 3. split() — разбиваем строку на массив
let parts: string[] = url.split("/");
console.log("Части URL:", parts);
// ["https:", "", "example.com", "users", "12345"]

// Извлекаем ID пользователя (последний элемент)
let userId: string = parts[parts.length - 1];
console.log(`ID пользователя: ${userId}`); // "12345"

// 4. Разбиваем строку с ограничением
let limitedParts: string[] = url.split("/", 3);
console.log("Первые 3 части:", limitedParts);
// ["https:", "", "example.com"]

Объяснение: slice() — самый гибкий метод для извлечения подстрок. Он поддерживает отрицательные индексы, что позволяет легко брать символы с конца. split() превращает строку в массив — это незаменимо при парсинге данных (например, CSV-файлов или URL).

Пример 3: Замена текста (replace, replaceAll)

let text: string = "Кошка гуляет по крыше. Кошка ловит мышей.";

// 1. replace() — заменяет только ПЕРВОЕ вхождение
let firstReplacement: string = text.replace("Кошка", "Собака");
console.log("После replace:", firstReplacement);
// "Собака гуляет по крыше. Кошка ловит мышей."

// 2. replaceAll() — заменяет ВСЕ вхождения (ES2021)
let allReplacement: string = text.replaceAll("Кошка", "Собака");
console.log("После replaceAll:", allReplacement);
// "Собака гуляет по крыше. Собака ловит мышей."

// 3. replace с регулярным выражением (флаг g — глобальный поиск)
let regexReplacement: string = text.replace(/Кошка/g, "Тигр");
console.log("С регулярным выражением:", regexReplacement);
// "Тигр гуляет по крыше. Тигр ловит мышей."

// 4. replace с функцией обратного вызова
let fancyReplacement: string = text.replace(/Кошка/g, (match: string) => {
    return match.toUpperCase() + " 🐱";
});
console.log("С функцией:", fancyReplacement);
// "КОШКА 🐱 гуляет по крыше. КОШКА 🐱 ловит мышей."

Объяснение: replace() без флага g заменяет только первое совпадение. replaceAll() — это современный метод (2021 год), который заменяет все вхождения без использования регулярных выражений. Если вам нужно что-то сложнее (например, замена с учётом регистра или динамическая замена), используйте регулярные выражения.

Пример 4: Работа с пробелами и форматирование

// 1. padStart() и padEnd() — дополнение строки до нужной длины
let number: string = "42";
let paddedStart: string = number.padStart(5, "0");
console.log(`padStart: ${paddedStart}`); // "00042"

let paddedEnd: string = number.padEnd(5, "!");
console.log(`padEnd: ${paddedEnd}`); // "42!!!"

// 2. repeat() — повторение строки
let laugh: string = "Ха";
let manyLaughs: string = laugh.repeat(5);
console.log(`repeat: ${manyLaughs}`); // "ХаХаХаХаХа"

// 3. trim(), trimStart(), trimEnd()
let messyString: string = "   пробелы   ";
console.log(`trim: "${messyString.trim()}"`); // "пробелы"
console.log(`trimStart: "${messyString.trimStart()}"`); // "пробелы   "
console.log(`trimEnd: "${messyString.trimEnd()}"`); // "   пробелы"

// 4. Практический пример: форматирование номера телефона
let phone: string = "1234567890";
let formattedPhone: string = 
    phone.slice(0, 3).padStart(4, "+7") + " " +
    phone.slice(3, 6) + " " +
    phone.slice(6, 8) + " " +
    phone.slice(8, 10);
console.log(`Форматированный телефон: ${formattedPhone}`);
// "+712 345 67 89"

Объяснение: padStart и padEnd часто используются для форматирования чисел (например, добавление ведущих нулей). repeat() удобен для создания повторяющихся паттернов. trim() — обязательный метод для очистки пользовательского ввода.

Пример 5: Проверка начала и конца строки

let filename: string = "document.pdf";
let email: string = "user@example.com";
let code: string = "  let x = 5;  ";

// 1. startsWith() — проверка начала строки
let isPdf: boolean = filename.startsWith("document");
console.log(`Файл начинается с "document"? ${isPdf}`); // true

let isHttp: boolean = email.startsWith("http");
console.log(`Email начинается с "http"? ${isHttp}`); // false

// 2. endsWith() — проверка конца строки
let isPdfFile: boolean = filename.endsWith(".pdf");
console.log(`Файл имеет расширение .pdf? ${isPdfFile}`); // true

let isComDomain: boolean = email.endsWith(".com");
console.log(`Email заканчивается на .com? ${isComDomain}`); // true

// 3. Практический пример: валидация расширения файла
function validateFile(fileName: string): boolean {
    const allowedExtensions: string[] = [".jpg", ".png", ".gif", ".pdf"];
    const lowerCaseName: string = fileName.toLowerCase();
    return allowedExtensions.some(ext => lowerCaseName.endsWith(ext));
}

console.log(`Файл "photo.JPG" допустим? ${validateFile("photo.JPG")}`); // true
console.log(`Файл "script.exe" допустим? ${validateFile("script.exe")}`); // false

// 4. startsWith с позицией
let sentence: string = "Hello, world!";
console.log(sentence.startsWith("world", 7)); // true (начинается с 7-го индекса)

Объяснение: startsWith() и endsWith() — это более читаемая альтернатива indexOf() для проверки начала и конца строки. Они принимают необязательный второй аргумент — позицию, с которой начинать поиск.

Пример 6: Сравнение строк и работа с Unicode

// 1. localeCompare() — сравнение с учётом локали
let word1: string = "яблоко";
let word2: string = "ананас";
let comparisonResult: number = word1.localeCompare(word2, "ru");
console.log(`Сравнение "яблоко" и "ананас": ${comparisonResult}`);
// 1 (потому что "я" > "а" в русском алфавите)

// 2. Сортировка массива строк с учётом локали
let fruits: string[] = ["яблоко", "ананас", "банан", "вишня"];
fruits.sort((a, b) => a.localeCompare(b, "ru"));
console.log("Отсортированные фрукты:", fruits);
// ["ананас", "банан", "вишня", "яблоко"]

// 3. charCodeAt() — получение Unicode-кода
let char: string = "A";
let unicodeCode: number = char.charCodeAt(0);
console.log(`Unicode код "A": ${unicodeCode}`); // 65

// 4. fromCharCode() — создание строки из кода (статический метод)
let charFromCode: string = String.fromCharCode(65, 66, 67);
console.log(`Из кодов 65, 66, 67: "${charFromCode}"`); // "ABC"

// 5. Работа с эмодзи (суррогатные пары)
let emoji: string = "🎉";
console.log(`Длина эмодзи: ${emoji.length}`); // 2 (потому что эмодзи занимает 2 символа UTF-16)
console.log(`Первый символ: ${emoji.charAt(0)}`); // "uD83C" (нечитаемый)
console.log(`Второй символ: ${emoji.charAt(1)}`); // "uDF89" (нечитаемый)

// Правильный способ перебора символов (ES6)
for (let symbol of emoji) {
    console.log(`Символ: ${symbol}`); // "🎉" (один символ)
}

Объяснение: localeCompare() — единственный правильный способ сортировки строк на русском (и других языках). Обычное сравнение через < и > работает неправильно для букв "ё", "й" и т.д. При работе с эмодзи всегда используйте for...of или оператор расширения ([...str]), а не charAt().

Пример 7: Комплексный пример — парсинг и валидация данных

// Представьте, что мы получаем данные от пользователя в виде строки
let userInput: string = "  Иван Петров, ivan@example.com, +7 (999) 123-45-67  ";

// Функция для очистки и разбора данных
function parseUserData(input: string): object | null {
    // 1. Убираем лишние пробелы
    const trimmed: string = input.trim();
    
    // 2. Проверяем, что строка не пустая
    if (trimmed.length === 0) {
        console.log("Ошибка: пустая строка");
        return null;
    }
    
    // 3. Разбиваем строку по запятой
    const parts: string[] = trimmed.split(",");
    
    // 4. Проверяем, что у нас ровно 3 части
    if (parts.length !== 3) {
        console.log("Ошибка: неверный формат. Ожидается: имя, email, телефон");
        return null;
    }
    
    // 5. Очищаем каждую часть от пробелов
    const name: string = parts[0].trim();
    const email: string = parts[1].trim();
    const phone: string = parts[2].trim();
    
    // 6. Валидация имени (не должно быть пустым)
    if (name.length === 0) {
        console.log("Ошибка: имя не может быть пустым");
        return null;
    }
    
    // 7. Валидация email (должен содержать @ и .)
    if (!email.includes("@") || !email.includes(".")) {
        console.log("Ошибка: неверный формат email");
        return null;
    }
    
    // 8. Валидация телефона (должен содержать только цифры, +, пробелы, скобки, тире)
    const phoneClean: string = phone.replace(/[^d+]/g, ""); // Удаляем всё, кроме цифр и +
    if (phoneClean.length < 10) {
        console.log("Ошибка: неверный формат телефона");
        return null;
    }
    
    //