Кратко
- Есть три типа кавычек. Строки, использующие обратные кавычки, могут занимать более одной строки в коде и включать выражения
${…}
. - Строки в JavaScript кодируются в UTF-16.
- Есть специальные символы, такие как разрыв строки
\n
. - Для получения символа используйте
[]
или методat
. - Для получения подстроки используйте
slice
илиsubstring
. - Для того, чтобы перевести строку в нижний или верхний регистр, используйте
toLowerCase/toUpperCase
. - Для поиска подстроки используйте
indexOf
илиincludes/startsWith/endsWith
, когда надо только проверить, есть ли вхождение. - Чтобы сравнить строки с учётом правил языка, используйте
localeCompare
.
Строки также имеют ещё кое-какие полезные методы:
str.trim()
— убирает пробелы в начале и конце строки.str.repeat(n)
— повторяет строкуn
раз.- …и другие, которые вы можете найти в справочнике.
Для строк предусмотрены методы для поиска и замены с использованием регулярных выражений. Но это отдельная большая тема, поэтому ей посвящена отдельная глава Регулярные выражения. Основы
Также, на данный момент важно знать, что строки основаны на кодировке Юникод, и поэтому иногда могут возникать проблемы со сравнениями. Подробнее о Юникоде в главе Юникод, внутреннее устройство строк.
Введение
В JavaScript любые текстовые данные являются строками. Не существует отдельного типа «символ», который есть в ряде других языков.
Внутренний формат для строк — всегда UTF-16, вне зависимости от кодировки страницы.
Кавычки
В JavaScript есть разные типы кавычек.
Строку можно создать с помощью одинарных, двойных либо обратных кавычек:
let single = 'single-quoted';
let double = "double-quoted";
let backticks = `backticks`;``
Одинарные и двойные кавычки работают, по сути, одинаково, а если использовать обратные кавычки, то в такую строку мы сможем вставлять произвольные выражения, обернув их в ${…}
:
function sum(a, b) {
return a + b;
}
alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.``
Ещё одно преимущество обратных кавычек — они могут занимать более одной строки, вот так:
let guestList = `Guests: * John * Pete * Mary `;
alert(guestList); // список гостей, состоящий из нескольких строк``
Выглядит вполне естественно, не правда ли? Что тут такого? Но если попытаться использовать точно так же одинарные или двойные кавычки, то будет ошибка:
let guestList = "Guests: // Error: Unexpected token ILLEGAL * John";
Одинарные и двойные кавычки в языке с незапамятных времён: тогда потребность в многострочных строках не учитывалась. Что касается обратных кавычек, они появились существенно позже, и поэтому они гибче.
Обратные кавычки также позволяют задавать «шаблонную функцию» перед первой обратной кавычкой. Используемый синтаксис: func`string`
. Автоматически вызываемая функция func
получает строку и встроенные в неё выражения и может их обработать. Подробнее об этом можно прочитать в документации. Если перед строкой есть выражение, то шаблонная строка называется «теговым шаблоном». Это позволяет использовать свою шаблонизацию для строк, но на практике теговые шаблоны применяются редко.
Спецсимволы
#/n Многострочные строки также можно создавать с помощью одинарных и двойных кавычек, используя так называемый «символ перевода строки», который записывается как \n
:
let guestList = "Guests:\n * John\n * Pete\n * Mary";
alert(guestList); // список гостей, состоящий из нескольких строк`
В частности, эти две строки эквивалентны, просто записаны по-разному:
// перевод строки добавлен с помощью символа перевода строки
let str1 = "Hello\nWorld";
// многострочная строка, созданная с использованием обратных кавычек
let str2 = `Hello World`; alert(str1 == str2); // true``
Есть и другие, реже используемые спецсимволы. Вот список:
Символ
Описание \n
Перевод строки \r
В текстовых файлах Windows для перевода строки используется комбинация символов \r\n
, а на других ОС это просто \n
. Это так по историческим причинам, ПО под Windows обычно понимает и просто \n
.
\'
, \"
Кавычки
\\
Обратный слеш
\t
Знак табуляции
\b
, \f
, \v
Backspace, Form Feed и Vertical Tab — оставлены для обратной совместимости, сейчас не используются.
Как вы можете видеть, все спецсимволы начинаются с обратного слеша, \
— так называемого «символа экранирования».
Он также используется, если необходимо вставить в строку кавычку.
К примеру:
alert( 'I_\'_m the Walrus!' ); // _I'm_ the Walrus!
Здесь перед входящей в строку кавычкой необходимо добавить обратный слеш — \'
— иначе она бы обозначала окончание строки.
Разумеется, требование экранировать относится только к таким же кавычкам, как те, в которые заключена строка. Так что мы можем применить и более элегантное решение, использовав для этой строки двойные или обратные кавычки:
alert( `I'm the Walrus!` ); // I'm the Walrus!
Заметим, что обратный слеш \
служит лишь для корректного прочтения строки интерпретатором, но он не записывается в строку после её прочтения. Когда строка сохраняется в оперативную память, в неё не добавляется символ \
. Вы можете явно видеть это в выводах alert
в примерах выше.
Но что, если нам надо добавить в строку собственно сам обратный слеш \
?
Это можно сделать, добавив перед ним… ещё один обратный слеш!
alert( `The backslash: \\` ); // The backslash: \
Длина строки
#length Свойство length
содержит длину строки:
alert( `My\n`.length ); // 3
Обратите внимание, \n
— это один спецсимвол, поэтому тут всё правильно: длина строки 3
.
length
— это свойство
Бывает так, что люди с практикой в других языках случайно пытаются вызвать его, добавляя круглые скобки: они пишут str.length()
вместо str.length
. Это не работает.
Так как str.length
— это числовое свойство, а не функция, добавлять скобки не нужно.
Доступ к символам
#pos Получить символ, который занимает позицию pos
, можно с помощью квадратных скобок: [pos]
. Также можно использовать метод str.at(pos). Первый символ занимает нулевую позицию:
let str = `Hello`; // получаем первый символ
alert( str[0] ); // H
alert( str.at(0) ); // H // получаем последний символ
alert( str[str.length - 1] ); // o
alert( str.at(-1) );``
Как вы можете видеть, преимущество метода .at(pos)
заключается в том, что он допускает отрицательную позицию. Если pos
– отрицательное число, то отсчет ведется от конца строки.
Таким образом, .at(-1)
означает последний символ, а .at(-2)
– тот, что перед ним, и т.д.
Квадратные скобки всегда возвращают undefined
для отрицательных индексов. Например:
let str = `Hello`;
alert( str[-2] ); // undefined alert( str.at(-2) ); // l``
Также можно перебрать строку посимвольно, используя for..of
:
for (let char of "Hello") {
alert(char); // H,e,l,l,o (char — сначала "H", потом "e", потом "l" и т.д.) }`
Строки неизменяемы
Содержимое строки в JavaScript нельзя изменить. Нельзя взять символ посередине и заменить его. Как только строка создана — она такая навсегда.
Давайте попробуем так сделать, и убедимся, что это не работает:
let str = 'Hi';
str[0] = 'h'; // ошибка
alert( str[0] ); // не работает`
Можно создать новую строку и записать её в ту же самую переменную вместо старой.
Например:
let str = 'Hi'; str = 'h' + str[1]; // заменяем строку alert( str ); // hi
В последующих разделах мы увидим больше примеров.
Изменение регистра
Методы toLowerCase() и toUpperCase() меняют регистр символов:
alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface`
Если мы захотим перевести в нижний регистр какой-то конкретный символ:
alert( 'Interface'[0].toLowerCase() ); // 'i'`
Поиск подстроки
Существует несколько способов поиска подстроки.
str.indexOf
Первый метод — str.indexOf(substr, pos).
Он ищет подстроку substr
в строке str
, начиная с позиции pos
, и возвращает позицию, на которой располагается совпадение, либо -1
при отсутствии совпадений.
Например:
let str = 'Widget with id';
alert( str.indexOf('Widget') ); // 0, потому что подстрока 'Widget' найдена в начале alert( str.indexOf('widget') ); // -1, совпадений нет, поиск чувствителен к регистру alert( str.indexOf("id") ); // 1, подстрока "id" найдена на позиции 1 (..idget with id)`
Необязательный второй аргумент позволяет начать поиск с определённой позиции.
Например, первое вхождение "id"
— на позиции 1
. Для того, чтобы найти следующее, начнём поиск с позиции 2
:
let str = 'Widget with id'; alert( str.indexOf('id', 2) ) // 12
Чтобы найти все вхождения подстроки, нужно запустить indexOf
в цикле. Каждый раз, получив очередную позицию, начинаем новый поиск со следующей:
let str = 'Ослик Иа-Иа посмотрел на виадук';
let target = 'Иа'; // цель поиска
let pos = 0; while (true) {
let foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert( `Найдено тут: ${foundPos}` );
pos = foundPos + 1; // продолжаем со следующей позиции }``
Тот же алгоритм можно записать и короче:
let str = "Ослик Иа-Иа посмотрел на виадук";
let target = "Иа";
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos ); }
str.lastIndexOf(substr, position)
Также есть похожий метод str.lastIndexOf(substr, position), который ищет с конца строки к её началу.
Он используется тогда, когда нужно получить самое последнее вхождение: перед концом строки или начинающееся до (включительно) определённой позиции.
При проверке indexOf
в условии if
есть небольшое неудобство. Такое условие не будет работать:
let str = "Widget with id";
if (str.indexOf("Widget")) {
alert("Совпадение есть"); // не работает }`
Мы ищем подстроку "Widget"
, и она здесь есть, прямо на позиции 0
. Но alert
не показывается, т. к. str.indexOf("Widget")
возвращает 0
, и if
решает, что тест не пройден.
Поэтому надо делать проверку на -1
:
let str = "Widget with id";
if (str.indexOf("Widget") != -1) {_
alert("Совпадение есть"); // теперь работает
}`
Трюк с побитовым НЕ
Существует старый трюк с использованием побитового оператора НЕ — ~
. Он преобразует число в 32-разрядное целое со знаком (signed 32-bit integer). Дробная часть, в случае, если она присутствует, отбрасывается. Затем все биты числа инвертируются.
На практике это означает простую вещь: для 32-разрядных целых чисел значение ~n
равно -(n+1)
.
В частности:
alert( ~2 ); // -3, то же, что -(2+1)
alert( ~1 ); // -2, то же, что -(1+1)
alert( ~0 ); // -1, то же, что -(0+1)
alert( ~-1 ); // 0, то же, что -(-1+1)
Таким образом, ~n
равняется 0 только при n == -1
(для любого n
, входящего в 32-разрядные целые числа со знаком).
Соответственно, прохождение проверки if ( ~str.indexOf("…") )
означает, что результат indexOf
отличен от -1
, совпадение есть.
Это иногда применяют, чтобы сделать проверку indexOf
компактнее:
let str = "Widget";
if (~str.indexOf("Widget")) {
alert( 'Совпадение есть' ); // работает
}
Обычно использовать возможности языка каким-либо неочевидным образом не рекомендуется, но этот трюк широко используется в старом коде, поэтому его важно понимать.
Просто запомните: if (~str.indexOf(…))
означает «если найдено».
Впрочем, если быть точнее, из-за того, что большие числа обрезаются до 32 битов оператором ~
, существуют другие числа, для которых результат тоже будет 0
, самое маленькое из которых — ~4294967295=0
. Поэтому такая проверка будет правильно работать только для строк меньшей длины.
На данный момент такой трюк можно встретить только в старом коде, потому что в новом он просто не нужен: есть метод .includes
(см. ниже).
includes, startsWith, endsWith
#includes Более современный метод str.includes(substr, pos) возвращает true
, если в строке str
есть подстрока substr
, либо false
, если нет.
Это — правильный выбор, если нам необходимо проверить, есть ли совпадение, но позиция не нужна:
alert( "Widget with id".includes("Widget") ); // true
alert( "Hello".includes("Bye") ); // false`
Необязательный второй аргумент str.includes
позволяет начать поиск с определённой позиции:
alert( "Midget".includes("id") ); // true
alert( "Midget".includes("id", 3) ); // false, поиск начат с позиции 3`
#startsWith#endWith Методы str.startsWith и str.endsWith проверяют, соответственно, начинается ли и заканчивается ли строка определённой строкой:
alert( "_Wid_get".startsWith("Wid") ); // true, "Wid" — начало "Widget"
alert( "Wid_get_".endsWith("get") ); // true, "get" — окончание "Widget"`
Получение подстроки
В JavaScript есть 3 метода для получения подстроки: substring
, substr
и slice
.
str.slice(start [, end])
#slice Возвращает часть строки от start
до (не включая) end
.
Например:
let str = "stringify"; // 'strin', символы от 0 до 5 (не включая 5)
alert( str.slice(0, 5) );
// 's', от 0 до 1, не включая 1, т. е. только один символ на позиции 0
alert( str.slice(0, 1) );`
Если аргумент end
отсутствует, slice
возвращает символы до конца строки:
let str = "st_ringify_";
alert( str.slice(2) ); // ringify, с позиции 2 и до конца`
Также для start/end
можно задавать отрицательные значения. Это означает, что позиция определена как заданное количество символов с конца строки:
let str = "strin_gif_y"; // начинаем с позиции 4 справа, а заканчиваем на позиции 1 справа
alert( str.slice(-4, -1) ); // gif`
str.substring(start [, end])
#substring Возвращает часть строки между start
и end
(не включая) end
.
Это — почти то же, что и slice
, но можно задавать start
больше end
.
Если start
больше end
, то метод substring
сработает так, как если бы аргументы были поменяны местами.
Например:
let str = "st_ring_ify"; // для substring эти два примера — одинаковы
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"
// …но не для slice:
alert( str.slice(2, 6) ); // "ring" (то же самое)
alert( str.slice(6, 2) ); // "" (пустая строка)`
Отрицательные значения substring
, в отличие от slice
, не поддерживает, они интерпретируются как 0
.
str.substr(start [, length])
#substr Возвращает часть строки от start
длины length
.
В противоположность предыдущим методам, этот позволяет указать длину вместо конечной позиции:
let str = "st_ring_ify"; // ring, получаем 4 символа, начиная с позиции 2
alert( str.substr(2, 4) );`
Значение первого аргумента может быть отрицательным, тогда позиция определяется с конца:
let str = "strin_gi_fy"; // gi, получаем 2 символа, начиная с позиции 4 с конца строки alert( str.substr(-4, 2) );`
Этот метод находится в Annex B спецификации языка. Это означает, что его должны поддерживать только браузерные движки JavaScript, и использовать его не рекомендуется. Но на практике он поддерживается везде.
Давайте подытожим, как работают эти методы, чтобы не запутаться: метод выбирает… отрицательные значения
slice(start, end)
от start
до end
(не включая end
)
можно передавать отрицательные значения
substring(start, end)
между start
и end
отрицательные значения равнозначны 0
substr(start, length)
length
символов, начиная от start
значение start
может быть отрицательным
Какой метод выбрать?
Все эти методы эффективно выполняют задачу. Формально у метода substr
есть небольшой недостаток: он описан не в собственно спецификации JavaScript, а в приложении к ней — Annex B. Это приложение описывает возможности языка для использования в браузерах, существующие в основном по историческим причинам. Таким образом, в другом окружении, отличном от браузера, он может не поддерживаться. Однако на практике он работает везде.
Из двух других вариантов, slice
более гибок, он поддерживает отрицательные аргументы, и его короче писать. Так что, в принципе, можно запомнить только его.
Сравнение строк
Как мы знаем из главы Операторы сравнения , строки сравниваются посимвольно в алфавитном порядке.
Тем не менее, есть некоторые нюансы.
-
Строчные буквы больше заглавных:
alert( 'a' > 'Z' ); // true
-
Буквы, имеющие диакритические знаки, идут «не по порядку»:
alert( 'Österreich' > 'Zealand' ); // true
Это может привести к своеобразным результатам при сортировке названий стран: нормально было бы ожидать, что
Zealand
будет послеÖsterreich
в списке.
Чтобы разобраться, что происходит, давайте ознакомимся с внутренним представлением строк в JavaScript.
Строки кодируются в UTF-16. Таким образом, у любого символа есть соответствующий код. Есть специальные методы, позволяющие получить символ по его коду и наоборот.
str.codePointAt(pos)
Возвращает код для символа, находящегося на позиции pos
:
// одна и та же буква в нижнем и верхнем регистре
// будет иметь разные коды
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90`
String.fromCodePoint(code)
Создаёт символ по его коду code
alert( String.fromCodePoint(90) ); // Z
Давайте сделаем строку, содержащую символы с кодами от 65
до 220
— это латиница и ещё некоторые распространённые символы:
let str = '';
for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i); }
alert( str );
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ // ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ``
Как видите, сначала идут заглавные буквы, затем несколько спецсимволов, затем строчные и Ö
ближе к концу вывода.
Теперь очевидно, почему a > Z
.
Символы сравниваются по их кодам. Больший код — больший символ.
Код a
(97) больше кода Z
(90).
- Все строчные буквы идут после заглавных, так как их коды больше.
- Некоторые буквы, такие как
Ö
, вообще находятся вне основного алфавита. У этой буквы код больше, чем у любой буквы отa
доz
.
Правильное сравнение
«Правильный» алгоритм сравнения строк сложнее, чем может показаться, так как разные языки используют разные алфавиты.
Поэтому браузеру нужно знать, какой язык использовать для сравнения.
К счастью, все современные браузеры (для IE10− нужна дополнительная библиотека Intl.JS) поддерживают стандарт ECMA 402, обеспечивающий правильное сравнение строк на разных языках с учётом их правил.
Для этого есть соответствующий метод.
Вызов str.localeCompare(str2) возвращает число, которое показывает, какая строка больше в соответствии с правилами языка:
- Отрицательное число, если
str
меньшеstr2
. - Положительное число, если
str
большеstr2
. 0
, если строки равны.
Например:
alert( 'Österreich'.localeCompare('Zealand') ); // -1
У этого метода есть два дополнительных аргумента, которые указаны в документации. Первый позволяет указать язык (по умолчанию берётся из окружения) — от него зависит порядок букв. Второй — определить дополнительные правила, такие как чувствительность к регистру, а также следует ли учитывать различия между "a"
и "á"
.