Три способа записать строку
В JavaScript есть три разделителя для строк. Два из них по сути взаимозаменяемы, а третий умеет куда больше.
Одинарные и двойные кавычки создают абсолютно одинаковые строки — выберите что-то одно и придерживайтесь этого стиля (в большинстве современных проектов по умолчанию используют одинарные кавычки либо отдают выбор на откуп форматтеру). А вот бэктики создают шаблонный литерал — и тут начинается самое интересное: такие строки javascript поддерживают интерполяцию и могут занимать несколько строк.
Под капотом все три варианта дают один и тот же примитивный строковый тип. Выбор разделителя — это чисто вопрос синтаксиса.
Экранирование и кавычки внутри строки
Внутри строки обратный слэш запускает escape-последовательность. Самые ходовые — это \n (перевод строки), \t (табуляция), \\ (сам обратный слэш), а также \" и \' — чтобы вставить ту же кавычку, которой вы открыли строку:
Большинства экранирований можно просто избежать, выбрав другой тип кавычек: 'She said "hi" and left.' — и никаких бэкслешей. Прагматизм важнее догмы.
Шаблонные литералы: суперсила бэктиков
Шаблонные литералы в JavaScript умеют две вещи, которые обычным строкам не под силу. Во-первых, они поддерживают интерполяцию выражений через ${...}:
Внутри ${...} можно писать любое JavaScript-выражение, а не только имя переменной. Арифметика, вызовы функций, тернарные операторы и даже вложенные шаблонные литералы — всё это работает:
Во-вторых, шаблонные литералы спокойно занимают несколько строк — без всяких \n:
Переносы строк в исходнике становятся переносами в итоговой строке. Для всего, что длиннее короткой подписи, бэктики — почти всегда правильный выбор.
Интерполяция вместо конкатенации
До появления шаблонных литералов единственным способом собрать динамическую строку был оператор +:
Оба варианта дают одинаковый результат. Шаблонный литерал читается как обычное предложение, которое мы собираем, а версия с + заставляет следить за кавычками, пробелами и операторами конкатенации. Используйте + только тогда, когда нужно добавить к строке что-то небольшое. Для всего, что длиннее, берите бэктики.
Строки в JavaScript неизменяемы
Любой метод, который на первый взгляд меняет строку, на самом деле возвращает новую:
Чтобы изменение сохранилось, результат нужно куда-то присвоить. На этом спотыкаются новички, которые пишут s.replace("a", "b") и ждут, что s поменяется. А он не меняется. Строки в JavaScript устроены как числа: само значение изменить нельзя, можно только переназначить переменную на новое значение.
Методы строк, которыми вы реально будете пользоваться
Полный набор методов для работы со строками огромный. Но в повседневной работе вам понадобится буквально пара штук:
Несколько моментов, которые стоит запомнить:
length— это свойство, а не метод, поэтому скобки не нужны.replaceс обычной строкой заменит только первое вхождение. Чтобы заменить все, используйтеreplaceAllили регулярное выражение с флагомg.slice(start, end)работает с полуоткрытым интервалом: символ с индексомendв результат не попадает.- Отрицательные индексы у
sliceотсчитываются с конца:s.slice(-3)вернёт последние три символа.
Доступ к символам строки и перебор
Строки в javascript ведут себя почти как массивы символов, доступные только для чтения. К ним можно обращаться по индексу и проходить в цикле, но присвоить значение конкретной позиции не получится:
for...of перебирает кодовые точки (code points) и корректно работает с большей частью Unicode. А вот обращение по индексу (s[i]) идёт по кодовым единицам UTF-16, из-за чего эмодзи и другие символы вне BMP могут разрезаться пополам. Для обычного текста разница несущественна, но когда речь о пользовательском вводе — лучше использовать for...of или [...s].
Tagged template literals
Есть ещё одна разновидность шаблонных литералов, о которой стоит знать, даже если вы нечасто ими пользуетесь. Тегированный шаблонный литерал (tagged template literal) — это вызов функции, которой отдельными аргументами передаются статические куски строки и подставленные значения:
Тег-функция решает, как будет выглядеть итоговая строка: она может экранировать HTML, безопасно собирать SQL, компилировать CSS или парсить GraphQL. На практике вы будете встречать этот приём в библиотеках (styled-components, graphql-tag, lit-html) гораздо чаще, чем писать свои теги. Но если вдруг нужна кастомная обработка строк — tagged template literals идеально для этого подходят.
Типичные ошибки
Короткий список того, на чём чаще всего спотыкаются:
- Забыли присвоить результат метода. Сам по себе
s.trim()ничего не меняет. - Конкатенация строк js через
+с числом."Age: " + 30 + 5даст"Age: 305", а не"Age: 35". С шаблонными литералами такой проблемы нет:`Age: ${30 + 5}`. - Путаница между
==и строгим сравнением. К равенству мы вернёмся отдельно, но запомните:"1" == 1— этоtrue, а"1" === 1— ужеfalse. Используйте===. - Считать, что
length— это количество символов. Для эмодзи и других символов из суррогатных пар"".lengthравно 2, а не 1. Если нужна длина «как её видит пользователь», берите[..."".length]илиArray.from(s).length.
Дальше: числа и BigInt
Строки — это один примитивный тип, числа — другой. Числовая система JavaScript со своими причудами: единый тип Number и для целых, и для дробных, плюс BigInt — когда обычного Number уже мало. Об этом — на следующей странице.
Часто задаваемые вопросы
Чем отличаются обычные кавычки от бэктиков в JavaScript?
Одинарные (') и двойные (") кавычки дают одно и то же — обычную строку, выбирайте любые. А вот бэктики (`) создают шаблонный литерал: внутри можно подставлять выражения через ${...} и писать текст на несколько строк без всяких \n. Как только появляется переменная внутри строки или многострочный текст — сразу берём бэктики.
Как подставить переменную в строку в JavaScript?
Через шаблонный литерал: оборачиваем строку в бэктики и пишем выражение внутри ${...}. Например, `Привет, ${name}!` — name вычислится и подставится на своё место. Внутри ${...} можно писать любое выражение: ${a + b}, ${user.name.toUpperCase()}, и даже вложенные шаблонные литералы.
Строки в JavaScript неизменяемые?
Да, неизменяемые. Любой метод строки — toUpperCase, slice, replace и прочие — возвращает новую строку, а оригинал остаётся как был. Если написать s.toUpperCase() и не присвоить результат — ничего не поменяется. Так же устроены строки в Python или Java.
Что такое tagged template literals?
Это когда перед бэктиками стоит имя функции, и она получает отдельно массив статических кусков строки и отдельно подставляемые значения. То есть tag`Привет, ${name}` превратится в вызов tag(["Привет, ", ""], name). На этом построены, например, styled-components и graphql-tag — они разбирают шаблон до того, как из него получится итоговое значение.