en.javascript.info/1-js/4-data-structures/3-string/article.md
Ilya Kantor 4466766972 fixes
2015-05-30 16:34:20 +03:00

24 KiB
Raw Blame History

Строки

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

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

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

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

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

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

var str = "012345";

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

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

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

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

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

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

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

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

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

var str = '*!*I\'m*/!* a JavaScript programmer';

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

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

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

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

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

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

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

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

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

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

Длина length

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

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

alert( str.length ); // 3

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

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

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

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

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

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

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

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

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

[warn header="Вызов метода -- всегда со скобками"]

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

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

[/warn]

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

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

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

//+ run
var str = "строка";

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

alert( str ); // ока

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

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

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

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

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

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

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

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

//+ run
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:

//+ run
var str = "Widget with id";

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

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

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

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

//+ run
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, т.е. совпадение есть.

Вот так:

//+ run
var str = "Widget";

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

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

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

[/smart]

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

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

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

//+ run
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, и так далее, пока что-то находит.

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

//+ run
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`.
//+ run
var str = "*!*s*/!*tringify";
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.

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

//+ run
var str = "st*!*ringify*/!*";
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
`substr(start [, length])`
Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов.
//+ run
var str = "st*!*ring*/!*ify";
str = str.substr(2,4); // ring, со 2й позиции 4 символа 
alert(str)

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

`slice(start [, end])`
Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`.

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

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

`substring(start, end)`
Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:
//+ run
alert( "testme".substring(-2) ); // "testme", -2 становится 0

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

//+ run
alert( "testme".substring(4, -1) ); // "test"
// -1 становится 0 -> получили substring(4, 0) 
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
`slice`
Отрицательные значения отсчитываются от конца строки:
//+ run
alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
//+ run
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.

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

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

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

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

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

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

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

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

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

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

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

String.fromCharCode(code)
Возвращает символ по коду `code`:
//+ run
alert( String.fromCharCode(1072) ); // 'а'

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

str.charCodeAt(pos)
Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля.
//+ run
alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'

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

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

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

//+ run
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). Удивительно: есть буква меньше чем А :)

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

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

//+ run
alert( "ö" > "z" ); // true

[smart header="Юникод в HTML"] Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference).

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

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

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

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

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

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

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

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

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

[warn header="Числа в виде строк сравниваются как строки"]

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

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

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

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

[/warn]

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

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

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

//+ run
var str = "Ёлки";

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

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

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

Итого

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

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