Строки и регулярные выражения
Методы строк, шаблонные литералы, неизменяемость, сравнение и регулярные выражения с их флагами и методами.
12 вопросов
JuniorТеорияОчень частоИзменяемы ли строки в JavaScript, и что происходит, когда метод меняет строку?
Изменяемы ли строки в JavaScript, и что происходит, когда метод меняет строку?
Строки — это неизменяемые примитивы. Ни метод, ни присваивание по индексу не меняют существующую строку на месте — методы вроде toUpperCase, slice и replace возвращают совершенно новую строку и оставляют оригинал нетронутым. Запись str[0] = 'x' молча ничего не делает (она бросает ошибку только в строгом режиме). Чтобы «изменить» строку, переменную переприсваивают новому значению.
Типичные ошибки
- ✗Считать, что строковые методы меняют оригинал, а не возвращают новую строку
- ✗Ожидать, что
str[0] = 'x'изменит первый символ - ✗Вызвать метод вроде
toUpperCaseи забыть использовать его возвращаемое значение
Уточняющие вопросы
- →Почему
str[0] = 'x'бросает ошибку в строгом режиме, но иначе молча проваливается? - →Как неизменяемость позволяет движкам безопасно делить или интернировать данные строк?
JuniorТеорияОчень частоЧто такое шаблонные литералы, и как в них работают интерполяция и многострочные строки?
Что такое шаблонные литералы, и как в них работают интерполяция и многострочные строки?
Шаблонные литералы — это строки в обратных кавычках ` ` вместо обычных. Они поддерживают интерполяцию: любое выражение внутри ${...} вычисляется и его результат вставляется в строку. Они также изначально многострочные — перенос строки в исходнике становится \n` в значении, без конкатенации и экранирования.
Типичные ошибки
- ✗Думать, что
${...}принимает только имена переменных, а не любое выражение - ✗Считать, что для многострочных строк в обратных кавычках всё ещё нужны
\nили+ - ✗Полагать, что
${...}вставляет текст скобок дословно, а не вычисляет его
Уточняющие вопросы
- →Можно ли вложить шаблонный литерал внутрь другого выражения
${...}? - →Как поместить буквальную обратную кавычку или
${внутрь шаблонного литерала?
JuniorТеорияЧастоЧто такое регулярное выражение, и чем литерал /x/ отличается от new RegExp?
Что такое регулярное выражение, и чем литерал /x/ отличается от new RegExp?
Регулярное выражение — это шаблон, описывающий набор строк, для поиска, проверки или замены текста. Литерал вроде /abc/i разбирается один раз при загрузке скрипта, поэтому подходит для фиксированных шаблонов. new RegExp('abc', 'i') строит тот же шаблон из строки во время выполнения, что нужно для динамических шаблонов — но обратные слеши тогда надо удваивать.
Типичные ошибки
- ✗Думать, что литерал и конструктор всегда ведут себя одинаково, игнорируя удвоение слешей
- ✗Забывать, что
new RegExpтребует удвоенных слешей, так как сначала разбирает строку - ✗Считать, что литерал надо строить во время выполнения, хотя фиксированный шаблон может быть литералом
Уточняющие вопросы
- →Почему
\dстановится'\\d'при записи как строка дляnew RegExp? - →Когда вы выберете
new RegExpвместо литерала в реальном коде?
JuniorТеорияИногдаЧто делают slice, substring, indexOf, toUpperCase и trim для строки?
Что делают slice, substring, indexOf, toUpperCase и trim для строки?
slice(start, end) и substring(start, end) возвращают подстроку между двумя индексами. indexOf(sub) возвращает первую позицию подстроки или -1, если её нет. toUpperCase() возвращает строку в верхнем регистре. trim() возвращает её без пробелов по краям. Все возвращают новые строки; ни один не меняет оригинал.
Типичные ошибки
- ✗Думать, что эти методы меняют исходную строку, а не возвращают новую
- ✗Ожидать от
indexOfвозврата0илиfalseдля отсутствующей подстроки вместо-1 - ✗Считать, что
trimубирает и внутренние пробелы, а не только по краям
Уточняющие вопросы
- →Чем
sliceиsubstringотличаются при передаче отрицательного индекса? - →Что возвращает
indexOfдля аргумента-пустой строки и почему?
JuniorТеорияИногдаКак includes, startsWith, endsWith и indexOf проверяют наличие подстроки?
Как includes, startsWith, endsWith и indexOf проверяют наличие подстроки?
includes(sub) возвращает true, если подстрока встречается где угодно. startsWith(sub) и endsWith(sub) возвращают true только в начале или конце. indexOf(sub) возвращает первый индекс подстроки или -1, если её нет, поэтому indexOf(sub) !== -1 — это старая булева проверка. Все чувствительны к регистру и принимают необязательный аргумент позиции.
Типичные ошибки
- ✗Ожидать, что эти методы игнорируют регистр, а не чувствительны к нему
- ✗Думать, что
includesвозвращает индекс, а не булево значение - ✗Забывать, что
indexOfвозвращает-1, поэтому простая truthy-проверка ошибается на индексе0
Уточняющие вопросы
- →Что задаёт необязательный второй аргумент у
startsWithиincludes? - →Почему
indexOf(sub) !== -1безопаснее, чемif (str.indexOf(sub))?
MiddleТеорияИногдаЧто задаёт каждый из флагов регулярных выражений g, i, m, s, u и y?
Что задаёт каждый из флагов регулярных выражений g, i, m, s, u и y?
g (global) находит все совпадения, а не останавливается на первом. i делает сопоставление нечувствительным к регистру. m (multiline) заставляет ^ и $ срабатывать на переносах строк, а не только на концах строки. s (dotAll) позволяет . совпадать с переносами. u включает полный режим Unicode и кодовых точек. y (sticky) привязывает каждое совпадение ровно к lastIndex. Флаги идут после закрывающего слеша, например /abc/gi.
Типичные ошибки
- ✗Путать
s(dotAll,.совпадает с переносом) сm(^/$по строкам) - ✗Думать, что
.совпадает с переносами по умолчанию без флагаs - ✗Считать, что
gиyведут себя одинаково, хотяyтребует совпадения, привязанного кlastIndex
Уточняющие вопросы
- →Чем sticky-флаг
yотличается отgпри сканировании строки? - →Что меняет флаг
uв том, как.и\wобрабатывают астральные символы?
MiddleТеорияИногдаЧем методы регулярки test/exec отличаются от строковых match/matchAll/replace/search?
Чем методы регулярки test/exec отличаются от строковых match/matchAll/replace/search?
test и exec вызывают на регулярке: test(str) возвращает булево, exec(str) — один массив совпадения (или null) и сдвигает lastIndex при g. match, matchAll, replace и search вызывают на строке: match возвращает совпадения или группы, matchAll — итератор всех совпадений, replace заменяет, а search возвращает первый индекс или -1.
Типичные ошибки
- ✗Вызывать
test/execна строке, а не на объекте регулярки - ✗Ожидать, что
matchс флагомgвключит группы захвата, какexec - ✗Думать, что
searchвозвращает булево, а не индекс или-1
Уточняющие вопросы
- →Почему повторные вызовы
execс регуляркой и флагомgв итоге возвращаютnull? - →Когда стоит предпочесть
matchAllциклу с вызовамиexec?
MiddleТеорияИногдаКак === и < сравнивают строки, и чем отличаются localeCompare и сравнение без учёта регистра?
Как === и < сравнивают строки, и чем отличаются localeCompare и сравнение без учёта регистра?
=== и </> сравнивают строки лексикографически по кодовым единицам UTF-16, поэтому порядок идёт по их номерам — заглавные сортируются раньше строчных, а буквы с диакритикой попадают странно. localeCompare вместо этого сравнивает по правилам локали, возвращая отрицательное, нулевое или положительное число. Для сравнения без учёта регистра приводят обе стороны к нижнему регистру или передают { sensitivity: 'base' } в localeCompare.
Типичные ошибки
- ✗Считать, что
</>сортируют по алфавиту, а не по сырому значению кодовой единицы - ✗Думать, что
localeCompareвозвращает булево вместо отрицательного/нуля/положительного числа - ✗Ожидать, что
===сравнивает ссылки как у объектов, а не содержимое символов
Уточняющие вопросы
- →Почему
'Z' < 'a'даётtrueпри операторе<? - →Как опция
sensitivityуlocaleCompareвключает порядок без учёта регистра или диакритики?
MiddleТеорияИногдаЧем отличаются substring, slice и устаревший substr, особенно с отрицательными индексами?
Чем отличаются substring, slice и устаревший substr, особенно с отрицательными индексами?
slice(start, end) и substring(start, end) оба берут начальный и конечный индекс, но slice трактует отрицательные индексы как смещение от конца, а substring приводит любой отрицательный аргумент к 0. substring также меняет аргументы местами, если start > end; slice этого не делает и возвращает ''. Устаревший substr(start, length) берёт длину, а не конечный индекс — предпочитайте slice.
Типичные ошибки
- ✗Ожидать, что
substringотсчитывает отрицательные индексы от конца, какslice - ✗Думать, что второй аргумент
substr— конечный индекс, а не длина - ✗Забывать, что
substringменяет аргументы местами приstart > end, в отличие отslice
Уточняющие вопросы
- →Что вернёт
'hello'.substring(3, 1)и почему это отличается отslice? - →Почему
substrустарел, хотя браузеры всё ещё его поддерживают?
MiddleТеорияРедкоЧто такое тегированный шаблон, и что получает его тег-функция, включая сырые строки?
Что такое тегированный шаблон, и что получает его тег-функция, включая сырые строки?
Тегированный шаблон — это вызов функции вида ` tag... . Тег-функция получает массив литеральных кусков строки первым аргументом, а интерполированные значения ${...} — остальными аргументами, что позволяет обработать или экранировать ввод до сборки вывода. У массива строк также есть свойство .raw с каждым куском без интерпретации escape-последовательностей, как использует String.raw`.
Типичные ошибки
- ✗Думать, что тег получает одну собранную строку, а не куски и значения по отдельности
- ✗Забывать, что массив строк несёт свойство
.rawс неинтерпретированными escape-последовательностями - ✗Путать, какой аргумент держит литералы, а какой — интерполированные значения
Уточняющие вопросы
- →Как восстановить исходный вывод, чередуя строки и значения?
- →Что даёт
String.rawдля `String.rawa\nb` и почему?
SeniorТеорияРедкоКак взаимодействуют группы захвата, жадные и ленивые квантификаторы и состояние lastIndex при флаге g?
Как взаимодействуют группы захвата, жадные и ленивые квантификаторы и состояние lastIndex при флаге g?
Скобки создают группы захвата, возвращаемые после полного совпадения. Квантификаторы вроде + и * жадные — захватывают максимум и откатываются; добавление ? (+?, *?) делает их ленивыми, захватывая минимум. Регулярка с флагом g имеет состояние: exec/test продолжают с изменяемого lastIndex, поэтому повторное использование одного общего экземпляра пропускает совпадения. matchAll обходит это, итерируя чисто.
Типичные ошибки
- ✗Повторно использовать одну
g-регулярку и терять совпадения, так какlastIndexсохраняется - ✗Считать, что квантификаторы ленивы по умолчанию, а не жадны
- ✗Думать, что
matchсgвозвращает группы захвата, какexecиmatchAll
Уточняющие вопросы
- →Почему случайный флаг
gможет заставлятьif (re.test(x))в цикле периодически проваливаться? - →Как замена
.*на.*?меняет, какое совпадение выдаёт шаблон?
SeniorТеорияРедкоКак кодовые единицы UTF-16, кодовые точки и суррогатные пары влияют на .length и перебор строки?
Как кодовые единицы UTF-16, кодовые точки и суррогатные пары влияют на .length и перебор строки?
Строки в JS — это последовательности кодовых единиц UTF-16, и .length считает кодовые единицы, а не символы. Астральный символ вроде эмодзи — это суррогатная пара из двух кодовых единиц, поэтому '😀'.length равно 2, а индексация его разрезает. Spread, for...of и Array.from перебирают по кодовым точкам и сохраняют его целым. normalize() приводит эквивалентные формы (например, é составную и разложенную) перед сравнением.
Типичные ошибки
- ✗Считать, что
.lengthсчитает видимые символы, а не кодовые единицы UTF-16 - ✗Индексировать или резать строку, разбивая суррогатную пару на сломанные половины
- ✗Сравнивать визуально одинаковые строки без
normalize, так что составная и разложенная формы различаются
Уточняющие вопросы
- →Почему
[...'😀'].lengthвозвращает1, а'😀'.length—2? - →Когда
normalize('NFC')меняет результат сравнения строк через===?