Menu
Русский

Строки и шаблонные литералы в JavaScript

Разбираемся, как устроены строки в JavaScript: кавычки, бэктики, интерполяция, многострочный текст и методы, которыми реально пользуешься каждый день.

Три способа записать строку

В JavaScript есть три разделителя для строк. Два из них по сути взаимозаменяемы, а третий умеет куда больше.

index.js
Output
Click Run to see the output here.

Одинарные и двойные кавычки создают абсолютно одинаковые строки — выберите что-то одно и придерживайтесь этого стиля (в большинстве современных проектов по умолчанию используют одинарные кавычки либо отдают выбор на откуп форматтеру). А вот бэктики создают шаблонный литерал — и тут начинается самое интересное: такие строки javascript поддерживают интерполяцию и могут занимать несколько строк.

Под капотом все три варианта дают один и тот же примитивный строковый тип. Выбор разделителя — это чисто вопрос синтаксиса.

Экранирование и кавычки внутри строки

Внутри строки обратный слэш запускает escape-последовательность. Самые ходовые — это \n (перевод строки), \t (табуляция), \\ (сам обратный слэш), а также \" и \' — чтобы вставить ту же кавычку, которой вы открыли строку:

index.js
Output
Click Run to see the output here.

Большинства экранирований можно просто избежать, выбрав другой тип кавычек: 'She said "hi" and left.' — и никаких бэкслешей. Прагматизм важнее догмы.

Шаблонные литералы: суперсила бэктиков

Шаблонные литералы в JavaScript умеют две вещи, которые обычным строкам не под силу. Во-первых, они поддерживают интерполяцию выражений через ${...}:

index.js
Output
Click Run to see the output here.

Внутри ${...} можно писать любое JavaScript-выражение, а не только имя переменной. Арифметика, вызовы функций, тернарные операторы и даже вложенные шаблонные литералы — всё это работает:

index.js
Output
Click Run to see the output here.

Во-вторых, шаблонные литералы спокойно занимают несколько строк — без всяких \n:

index.js
Output
Click Run to see the output here.

Переносы строк в исходнике становятся переносами в итоговой строке. Для всего, что длиннее короткой подписи, бэктики — почти всегда правильный выбор.

Интерполяция вместо конкатенации

До появления шаблонных литералов единственным способом собрать динамическую строку был оператор +:

index.js
Output
Click Run to see the output here.

Оба варианта дают одинаковый результат. Шаблонный литерал читается как обычное предложение, которое мы собираем, а версия с + заставляет следить за кавычками, пробелами и операторами конкатенации. Используйте + только тогда, когда нужно добавить к строке что-то небольшое. Для всего, что длиннее, берите бэктики.

Строки в JavaScript неизменяемы

Любой метод, который на первый взгляд меняет строку, на самом деле возвращает новую:

index.js
Output
Click Run to see the output here.

Чтобы изменение сохранилось, результат нужно куда-то присвоить. На этом спотыкаются новички, которые пишут s.replace("a", "b") и ждут, что s поменяется. А он не меняется. Строки в JavaScript устроены как числа: само значение изменить нельзя, можно только переназначить переменную на новое значение.

Методы строк, которыми вы реально будете пользоваться

Полный набор методов для работы со строками огромный. Но в повседневной работе вам понадобится буквально пара штук:

index.js
Output
Click Run to see the output here.

Несколько моментов, которые стоит запомнить:

  • length — это свойство, а не метод, поэтому скобки не нужны.
  • replace с обычной строкой заменит только первое вхождение. Чтобы заменить все, используйте replaceAll или регулярное выражение с флагом g.
  • slice(start, end) работает с полуоткрытым интервалом: символ с индексом end в результат не попадает.
  • Отрицательные индексы у slice отсчитываются с конца: s.slice(-3) вернёт последние три символа.

Доступ к символам строки и перебор

Строки в javascript ведут себя почти как массивы символов, доступные только для чтения. К ним можно обращаться по индексу и проходить в цикле, но присвоить значение конкретной позиции не получится:

index.js
Output
Click Run to see the output here.

for...of перебирает кодовые точки (code points) и корректно работает с большей частью Unicode. А вот обращение по индексу (s[i]) идёт по кодовым единицам UTF-16, из-за чего эмодзи и другие символы вне BMP могут разрезаться пополам. Для обычного текста разница несущественна, но когда речь о пользовательском вводе — лучше использовать for...of или [...s].

Tagged template literals

Есть ещё одна разновидность шаблонных литералов, о которой стоит знать, даже если вы нечасто ими пользуетесь. Тегированный шаблонный литерал (tagged template literal) — это вызов функции, которой отдельными аргументами передаются статические куски строки и подставленные значения:

index.js
Output
Click Run to see the output here.

Тег-функция решает, как будет выглядеть итоговая строка: она может экранировать 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 — они разбирают шаблон до того, как из него получится итоговое значение.

Учитесь программировать с Coddy

НАЧАТЬ