Строки

В JavaScript любые текстовые данные являются строками. Не существует отдельного типа «символ», который есть в ряде других языков.

Внутренним форматом строк, вне зависимости от кодировки страницы, является Юникод (Unicode).

Создание строк

Строки создаются при помощи двойных или одинарных кавычек:

var text = "моя строка";

var anotherText = 'еще строка';

var str = "012345";

В JavaScript нет разницы между двойными и одинарными кавычками.

Специальные символы

Строки могут содержать специальные символы. Самый часто используемый из таких символов – это «перевод строки».

Он обозначается как \n, например:

alert( 'Привет\nМир' ); // выведет "Мир" на новой строке

Есть и более редкие символы, вот их список:

Специальные символы
СимволОписание
\bBackspace
\fForm feed
\nNew line
\rCarriage return
\tTab
\uNNNNСимвол в кодировке Юникод с шестнадцатеричным кодом `NNNN`. Например, `\u00A9` -- юникодное представление символа копирайт ©

Экранирование специальных символов

Если строка в одинарных кавычках, то внутренние одинарные кавычки внутри должны быть экранированы, то есть снабжены обратным слешем \', вот так:

var str = 'I\'m a JavaScript programmer';

В двойных кавычках – экранируются внутренние двойные:

var str = "I'm a JavaScript \"programmer\" ";
alert( str ); // I'm a JavaScript "programmer"

Экранирование служит исключительно для правильного восприятия строки JavaScript. В памяти строка будет содержать сам символ без '\'. Вы можете увидеть это, запустив пример выше.

Сам символ обратного слэша '\' является служебным, поэтому всегда экранируется, т.е пишется как \\:

var str = ' символ \\ ';

alert( str ); // символ \

Заэкранировать можно любой символ. Если он не специальный, то ничего не произойдёт:

alert( "\a" ); // a
// идентично alert(  "a"  );

Методы и свойства

Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе Введение в методы и свойства.

Длина length

Одно из самых частых действий со строкой – это получение ее длины:

var str = "My\n"; // 3 символа. Третий - перевод строки

alert( str.length ); // 3

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

Чтобы получить символ, используйте вызов charAt(позиция). Первый символ имеет позицию 0:

var str = "jQuery";
alert( str.charAt(0) ); // "j"

В JavaScript нет отдельного типа «символ», так что charAt возвращает строку, состоящую из выбранного символа.

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

var str = "Я - современный браузер!";
alert( str[0] ); // "Я"

Разница между этим способом и charAt заключается в том, что если символа нет – charAt выдает пустую строку, а скобки – undefined:

alert( "".charAt(0) ); // пустая строка
alert( "" [0] ); // undefined

Вообще же метод charAt существует по историческим причинам, ведь квадратные скобки – проще и короче.

Вызов метода – всегда со скобками

Обратите внимание, str.length – это свойство строки, а str.charAt(pos)метод, т.е. функция.

Обращение к методу всегда идет со скобками, а к свойству – без скобок.

Изменения строк

Содержимое строки в JavaScript нельзя изменять. Нельзя взять символ посередине и заменить его. Как только строка создана – она такая навсегда.

Можно лишь создать целиком новую строку и присвоить в переменную вместо старой, например:

var str = "строка";

str = str[3] + str[4] + str[5];

alert( str ); // ока

Смена регистра

Методы toLowerCase() и toUpperCase() меняют регистр строки на нижний/верхний:

alert( "Интерфейс".toUpperCase() ); // ИНТЕРФЕЙС

Пример ниже получает первый символ и приводит его к нижнему регистру:

alert( "Интерфейс" [0].toLowerCase() ); // 'и'

Поиск подстроки

Для поиска подстроки есть метод indexOf(подстрока[, начальная_позиция]).

Он возвращает позицию, на которой находится подстрока или -1, если ничего не найдено. Например:

var str = "Widget with id";

alert( str.indexOf("Widget") ); // 0, т.к. "Widget" найден прямо в начале str
alert( str.indexOf("id") ); // 1, т.к. "id" найден, начиная с позиции 1
alert( str.indexOf("widget") ); // -1, не найдено, так как поиск учитывает регистр

Необязательный второй аргумент позволяет искать, начиная с указанной позиции. Например, первый раз "id" появляется на позиции 1. Чтобы найти его следующее появление – запустим поиск с позиции 2:

var str = "Widget with id";

alert(str.indexOf("id", 2)) // 12, поиск начат с позиции 2

Также существует аналогичный метод lastIndexOf, который ищет не с начала, а с конца строки.

На заметку:

Для красивого вызова indexOf применяется побитовый оператор НЕ '~'.

Дело в том, что вызов ~n эквивалентен выражению -(n+1), например:

alert( ~2 ); // -(2+1) = -3
alert( ~1 ); // -(1+1) = -2
alert( ~0 ); // -(0+1) = -1
alert( ~-1 ); // -(-1+1) = 0

Как видно, ~n – ноль только в случае, когда n == -1.

То есть, проверка if ( ~str.indexOf(...) ) означает, что результат indexOf отличен от -1, т.е. совпадение есть.

Вот так:

var str = "Widget";

if (~str.indexOf("get")) {
  alert( 'совпадение есть!' );
}

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

Однако, в данном случае, все в порядке. Просто запомните: '~' читается как «не минус один», а "if ~str.indexOf" читается как "если найдено".

Поиск всех вхождений

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

Пример такого цикла:

var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска

var pos = 0;
while (true) {
  var foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( foundPos ); // нашли на этой позиции
  pos = foundPos + 1; // продолжить поиск со следующей
}

Такой цикл начинает поиск с позиции 0, затем найдя подстроку на позиции foundPos, следующий поиск продолжит с позиции pos = foundPos+1, и так далее, пока что-то находит.

Впрочем, тот же алгоритм можно записать и короче:

var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска

var pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
  alert( pos );
}

Взятие подстроки: substr, substring, slice.

В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними.

substring(start [, end])

Метод substring(start, end) возвращает подстроку с позиции start до, но не включая end.

var str = "stringify";
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.

Если аргумент end отсутствует, то идет до конца строки:

var str = "stringify";
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
substr(start [, length])

Первый аргумент имеет такой же смысл, как и в substring, а второй содержит не конечную позицию, а количество символов.

var str = "stringify";
str = str.substr(2,4); // ring, со 2-й позиции 4 символа
alert(str)

Если второго аргумента нет – подразумевается «до конца строки».

slice(start [, end])

Возвращает часть строки от позиции start до, но не включая, позиции end. Смысл параметров – такой же как в substring.

Отрицательные аргументы

Различие между substring и slice – в том, как они работают с отрицательными и выходящими за границу строки аргументами:

substring(start, end)

Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:

alert( "testme".substring(-2) ); // "testme", -2 становится 0

Кроме того, если start > end, то аргументы меняются местами, т.е. возвращается участок строки между start и end:

alert( "testme".substring(4, -1) ); // "test"
// -1 становится 0 -> получили substring(4, 0)
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
slice

Отрицательные значения отсчитываются от конца строки:

alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.

Это гораздо более удобно, чем странная логика substring.

Отрицательное значение первого параметра поддерживается в substr во всех браузерах, кроме IE8-.

Если выбирать из этих трёх методов один, для использования в большинстве ситуаций – то это будет slice: он и отрицательные аргументы поддерживает и работает наиболее очевидно.

Кодировка Юникод

Как мы знаем, символы сравниваются в алфавитном порядке 'А' < 'Б' < 'В' < ... < 'Я'.

Но есть несколько странностей…

  1. Почему буква 'а' маленькая больше буквы 'Я' большой?

    alert( 'а' > 'Я' ); // true
  2. Буква 'ё' находится в алфавите между е и ж: абвгдеёжз…. Но почему тогда 'ё' больше 'я'?

    alert( 'ё' > 'я' ); // true

Чтобы разобраться с этим, обратимся к внутреннему представлению строк в JavaScript.

Все строки имеют внутреннюю кодировку Юникод.

Неважно, на каком языке написана страница, находится ли она в windows-1251 или utf-8. Внутри JavaScript-интерпретатора все строки приводятся к единому «юникодному» виду. Каждому символу соответствует свой код.

Есть метод для получения символа по его коду:

String.fromCharCode(code)

Возвращает символ по коду code:

alert( String.fromCharCode(1072) ); // 'а'

…И метод для получения цифрового кода из символа:

str.charCodeAt(pos)

Возвращает код символа на позиции pos. Отсчет позиции начинается с нуля.

alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'

Теперь вернемся к примерам выше. Почему сравнения 'ё' > 'я' и 'а' > 'Я' дают такой странный результат?

Дело в том, что символы сравниваются не по алфавиту, а по коду. У кого код больше – тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее – Кириллица в Юникоде.

Выведем отрезок символов юникода с кодами от 1034 до 1113:

var str = '';
for (var i = 1034; i <= 1113; i++) {
  str += String.fromCharCode(i);
}
alert( str );

Результат:

ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ

Мы можем увидеть из этого отрезка две важных вещи:

  1. Строчные буквы идут после заглавных, поэтому они всегда больше.

    В частности, 'а'(код 1072) > 'Я'(код 1071).

    То же самое происходит и в английском алфавите, там 'a' > 'Z'.

  2. Ряд букв, например ё, находятся вне основного алфавита.

    В частности, маленькая буква ё имеет код, больший чем я, поэтому 'ё'(код 1105) > 'я'(код 1103).

    Кстати, большая буква Ё располагается в Unicode до А, поэтому 'Ё'(код 1025) < 'А'(код 1040). Удивительно: есть буква меньше чем А :)

Буква ё не уникальна, точки над буквой используются и в других языках, приводя к тому же результату.

Например, при работе с немецкими названиями:

alert( "ö" > "z" ); // true
Юникод в HTML

Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя «числовую ссылку» (numeric character reference).

Для этого нужно написать сначала &#, затем код, и завершить точкой с запятой ';'. Например, символ 'а' в виде числовой ссылки: &#1072;.

Если код хотят дать в 16-ричной системе счисления, то начинают с &#x.

В юникоде есть много забавных и полезных символов, например, символ ножниц: ✂ (&#x2702;), дроби: ½ (&#xBD;) ¾ (&#xBE;) и другие. Их можно использовать вместо картинок в дизайне.

Посимвольное сравнение

Сравнение строк работает лексикографически, иначе говоря, посимвольно.

Сравнение строк s1 и s2 обрабатывается по следующему алгоритму:

  1. Сравниваются первые символы: s1[0] и s2[0]. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить true или false. Если же они одинаковые, то…
  2. Сравниваются вторые символы s1[1] и s2[1]
  3. Затем третьи s1[2] и s2[2] и так далее, пока символы не будут наконец разными, и тогда какой символ больше – та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих – они равны.

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

"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н'
"Дома" > "До" // true, т.к. начало совпадает, но в 1-й строке больше символов
Числа в виде строк сравниваются как строки

Бывает, что числа приходят в скрипт в виде строк, например как результат prompt. В этом случае результат их сравнения будет неверным:

alert( "2" > "14" ); // true, так как это строки, и для первых символов верно "2" > "1"

Если хотя бы один аргумент – не строка, то другой будет преобразован к числу:

alert( 2 > "14" ); // false

Правильное сравнение

Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку Intl.JS) поддерживают стандарт ECMA 402, поддерживающий сравнение строк на разных языках, с учётом их правил.

Способ использования:

var str = "Ёлки";

alert( str.localeCompare("Яблони") ); // -1

Метод str1.localeCompare(str2) возвращает -1, если str1 < str2, 1, если str1 > str2 и 0, если они равны.

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

Итого

  • Строки в JavaScript имеют внутреннюю кодировку Юникод. При написании строки можно использовать специальные символы, например \n и вставлять юникодные символы по коду.
  • Мы познакомились со свойством length и методами charAt, toLowerCase/toUpperCase, substring/substr/slice (предпочтителен slice). Есть и другие методы, например trim обрезает пробелы с начала и конца строки.
  • Строки сравниваются побуквенно. Поэтому если число получено в виде строки, то такие числа могут сравниваться некорректно, нужно преобразовать его к типу number.
  • При сравнении строк следует иметь в виду, что буквы сравниваются по их кодам. Поэтому большая буква меньше маленькой, а буква ё вообще вне основного алфавита.
  • Для правильного сравнения существует целый стандарт ECMA 402. Это не такое простое дело, много языков и много правил. Он поддерживается во всех современных браузерах, кроме IE10-, в которых нужна библиотека https://github.com/andyearnshaw/Intl.js/. Такое сравнение работает через вызов str1.localeCompare(str2).

Больше информации о методах для строк можно получить в справочнике: http://javascript.ru/String.

Задачи

важность: 5

Напишите функцию ucFirst(str), которая возвращает строку str с заглавным первым символом, например:

ucFirst("вася") == "Вася";
ucFirst("") == ""; // нет ошибок при пустой строке

P.S. В JavaScript нет встроенного метода для этого. Создайте функцию, используя toUpperCase() и charAt().

Открыть песочницу с тестами для задачи.

Мы не можем просто заменить первый символ, т.к. строки в JavaScript неизменяемы.

Но можно пересоздать строку на основе существующей, с заглавным первым символом:

var newStr = str[0].toUpperCase() + str.slice(1);

Однако, есть небольшая проблемка – в случае, когда строка пуста, будет ошибка.

Ведь str[0] == undefined, а у undefined нет метода toUpperCase().

Выхода два. Первый – использовать str.charAt(0), он всегда возвращает строку, для пустой строки – пустую, но не undefined. Второй – отдельно проверить на пустую строку, вот так:

function ucFirst(str) {
  // только пустая строка в логическом контексте даст false
  if (!str) return str;

  return str[0].toUpperCase() + str.slice(1);
}

alert( ucFirst("вася") );

P.S. Возможны и более короткие решения, использующие методы для работы со строками, которые мы пройдём далее.

Открыть решение с тестами в песочнице.

важность: 5

Напишите функцию checkSpam(str), которая возвращает true, если строка str содержит „viagra“ или „XXX“, а иначе false.

Функция должна быть нечувствительна к регистру:

checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false

Открыть песочницу с тестами для задачи.

Метод indexOf ищет совпадение с учетом регистра. То есть, в строке 'xXx' он не найдет 'XXX'.

Для проверки, сначала приведем строку str к нижнему регистру, а затем уже будем искать.

function checkSpam(str) {
  var lowerStr = str.toLowerCase();

  return !!(~lowerStr.indexOf('viagra') || ~lowerStr.indexOf('xxx'));
}

alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );

Открыть решение с тестами в песочнице.

важность: 5

Создайте функцию truncate(str, maxlength), которая проверяет длину строки str, и если она превосходит maxlength – заменяет конец str на "...", так чтобы ее длина стала равна maxlength.

Результатом функции должна быть (при необходимости) усечённая строка.

Например:

truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хоте..."

truncate("Всем привет!", 20) = "Всем привет!"

Эта функция имеет применение в жизни. Она используется, чтобы усекать слишком длинные темы сообщений.

Открыть песочницу с тестами для задачи.

Так как окончательная длина строки должна быть maxlength, то нужно её обрезать немного короче, чтобы дать место для троеточия.

function truncate(str, maxlength) {
  if (str.length > maxlength) {
    return str.slice(0, maxlength - 3) + '...';
    // итоговая длина равна maxlength
  }

  return str;
}

alert( truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) );
alert( truncate("Всем привет!", 20) );

Можно было бы написать этот код ещё короче:

function truncate(str, maxlength) {
  return (str.length > maxlength) ?
    str.slice(0, maxlength - 3) + '...' : str;
}

P.S. Кстати, в кодировке Unicode существует специальный символ «троеточие»: (HTML: &hellip;), который можно использовать вместо трёх точек. Если его использовать, то можно отрезать только один символ.

Открыть решение с тестами в песочнице.

важность: 4

Есть стоимость в виде строки: "$120". То есть, первым идёт знак валюты, а затем – число.

Создайте функцию extractCurrencyValue(str), которая будет из такой строки выделять число-значение, в данном случае 120.

Открыть песочницу с тестами для задачи.

Возьмём часть строки после первого символа и приведём к числу: +str.slice(1).

Открыть решение с тестами в песочнице.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.