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

534 lines
No EOL
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Строки
В JavaScript любые текстовые данные являются строками. Не существует отдельного типа "символ", который есть в ряде других языков.
Внутренним форматом строк, вне зависимости от кодировки страницы, является [Юникод (Unicode)](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).
[cut]
## Создание строк
Строки создаются при помощи двойных или одинарных кавычек:
```js
var text = "моя строка";
var anotherText = 'еще строка';
var str = "012345";
```
В JavaScript нет разницы между двойными и одинарными кавычками.
### Специальные символы
Строки могут содержать специальные символы. Самый часто используемый из таких символов -- это "перевод строки".
Он обозначается как `\n`, например:
```js
//+ run
alert( 'Привет\nМир' ); // выведет "Мир" на новой строке
```
Есть и более редкие символы, вот их список:
<table>
<CAPTION>Специальные символы</CAPTION>
<thead>
<tr><th>Символ</th><th>Описание</th></tr>
</thead>
<tbody>
<tr><td>\b</td><td>Backspace</td></tr>
<tr><td>\f</td><td>Form feed</td></tr>
<tr><td>\n</td><td>New line</td></tr>
<tr><td>\r</td><td>Carriage return</td></tr>
<tr><td>\t</td><td>Tab</td></tr>
<tr><td>\uNNNN</td><td>Символ в кодировке Юникод с шестнадцатеричным кодом `NNNN`. Например, `\u00A9` -- юникодное представление символа копирайт &#xA9;
</td></tr>
</tbody>
</table>
### Экранирование специальных символов
Если строка в одинарных кавычках, то внутренние одинарные кавычки внутри должны быть *экранированы*, то есть снабжены обратным слешем `\'`, вот так:
```js
var str = '*!*I\'m*/!* a JavaScript programmer';
```
В двойных кавычках -- экранируются внутренние двойные:
```js
//+ run
var str = "I'm a JavaScript \"programmer\" ";
alert( str ); // I'm a JavaScript "programmer"
```
Экранирование служит исключительно для правильного восприятия строки JavaScript. В памяти строка будет содержать сам символ без `'\'`. Вы можете увидеть это, запустив пример выше.
Сам символ обратного слэша `'\'` является служебным, поэтому всегда экранируется, т.е пишется как `\\`:
```js
//+ run
var str = ' символ \\ ';
alert( str ); // символ \
```
Заэкранировать можно любой символ. Если он не специальный, то ничего не произойдёт:
```js
//+ run
alert( "\a" ); // a
// идентично alert( "a" );
```
## Методы и свойства
Здесь мы рассмотрим методы и свойства строк, с некоторыми из которых мы знакомились ранее, в главе [](/properties-and-methods).
### Длина length
Одно из самых частых действий со строкой -- это получение ее длины:
```js
//+ run
var str = "My\n"; // 3 символа. Третий - перевод строки
alert( str.length ); // 3
```
### Доступ к символам
Чтобы получить символ, используйте вызов `charAt(позиция)`. Первый символ имеет позицию `0`:
```js
//+ run
var str = "jQuery";
alert( str.charAt(0) ); // "j"
```
В JavaScript **нет отдельного типа "символ"**, так что `charAt` возвращает строку, состоящую из выбранного символа.
Также для доступа к символу можно также использовать квадратные скобки:
```js
//+ run
var str = "Я - современный браузер!";
alert( str[0] ); // "Я"
```
Разница между этим способом и `charAt` заключается в том, что если символа нет -- `charAt` выдает пустую строку, а скобки -- `undefined`:
```js
//+ run
alert( "".charAt(0) ); // пустая строка
alert( "" [0] ); // undefined
```
Вообще же метод `charAt` существует по историческим причинам, ведь квадратные скобки -- проще и короче.
[warn header="Вызов метода -- всегда со скобками"]
Обратите внимание, `str.length` -- это *свойство* строки, а `str.charAt(pos)` -- *метод*, т.е. функция.
Обращение к методу всегда идет со скобками, а к свойству -- без скобок.
[/warn]
### Изменения строк
Содержимое строки в JavaScript нельзя изменять. Нельзя взять символ посередине и заменить его. Как только строка создана -- она такая навсегда.
Можно лишь создать целиком новую строку и присвоить в переменную вместо старой, например:
```js
//+ run
var str = "строка";
str = str[3] + str[4] + str[5];
alert( str ); // ока
```
### Смена регистра
Методы `toLowerCase()` и `toUpperCase()` меняют регистр строки на нижний/верхний:
```js
//+ run
alert( "Интерфейс".toUpperCase() ); // ИНТЕРФЕЙС
```
Пример ниже получает первый символ и приводит его к нижнему регистру:
```js
alert( "Интерфейс" [0].toLowerCase() ); // 'и'
```
### Поиск подстроки
Для поиска подстроки есть метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/indexOf">indexOf(подстрока[, начальная_позиция])</a>.
Он возвращает позицию, на которой находится `подстрока` или `-1`, если ничего не найдено. Например:
```js
//+ 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`:
```js
//+ run
var str = "Widget with id";
alert(str.indexOf("id", 2)) // 12, поиск начат с позиции 2
```
Также существует аналогичный метод <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/lastIndexOf">lastIndexOf</a>, который ищет не с начала, а с конца строки.
[smart]
Для красивого вызова `indexOf` применяется побитовый оператор НЕ `'~'`.
Дело в том, что вызов `~n` эквивалентен выражению `-(n+1)`, например:
```js
//+ 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`, т.е. совпадение есть.
Вот так:
```js
//+ run
var str = "Widget";
if (~str.indexOf("get")) {
alert( 'совпадение есть!' );
}
```
Вообще, использовать возможности языка неочевидным образом не рекомендуется, поскольку ухудшает читаемость кода.
Однако, в данном случае, все в порядке. Просто запомните: `'~'` читается как "не минус один", а `"if ~str.indexOf"` читается как `"если найдено"`.
[/smart]
### Поиск всех вхождений
Чтобы найти все вхождения подстроки, нужно запустить `indexOf` в цикле. Как только получаем очередную позицию -- начинаем следующий поиск со следующей.
Пример такого цикла:
```js
//+ 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`, и так далее, пока что-то находит.
Впрочем, тот же алгоритм можно записать и короче:
```js
//+ run
var str = "Ослик Иа-Иа посмотрел на виадук"; // ищем в этой строке
var target = "Иа"; // цель поиска
*!*
var pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos );
}
*/!*
```
### Взятие подстроки: substr, substring, slice.
В JavaScript существуют целых 3 (!) метода для взятия подстроки, с небольшими отличиями между ними.
<dl>
<dt>`substring(start [, end])`
<dd>
Метод `substring(start, end)` возвращает подстроку с позиции `start` до, но не включая `end`.
```js
//+ run
var str = "*!*s*/!*tringify";
alert(str.substring(0,1)); // "s", символы с позиции 0 по 1 не включая 1.
```
Если аргумент `end` отсутствует, то идет до конца строки:
```js
//+ run
var str = "st*!*ringify*/!*";
alert(str.substring(2)); // ringify, символы с позиции 2 до конца
```
</dd>
<dt>`substr(start [, length])`</dt>
<dd>Первый аргумент имеет такой же смысл, как и в `substring`, а второй содержит не конечную позицию, а количество символов.
```js
//+ run
var str = "st*!*ring*/!*ify";
str = str.substr(2,4); // ring, со 2й позиции 4 символа
alert(str)
```
Если второго аргумента нет -- подразумевается "до конца строки".</dd>
<dt>`slice(start [, end])`</dt>
<dd>Возвращает часть строки от позиции `start` до, но не включая, позиции `end`. Смысл параметров -- такой же как в `substring`.</dd>
</dl>
### Отрицательные аргументы
Различие между `substring` и `slice` -- в том, как они работают с отрицательными и выходящими за границу строки аргументами:
<dl>
<dt>`substring(start, end)`</dt>
<dd>Отрицательные аргументы интерпретируются как равные нулю. Слишком большие значения усекаются до длины строки:
```js
//+ run
alert( "testme".substring(-2) ); // "testme", -2 становится 0
```
Кроме того, если <code>start &gt; end</code>, то аргументы меняются местами, т.е. возвращается участок строки *между* `start` и `end`:
```js
//+ run
alert( "testme".substring(4, -1) ); // "test"
// -1 становится 0 -> получили substring(4, 0)
// 4 > 0, так что аргументы меняются местами -> substring(0, 4) = "test"
```
</dd>
<dt>`slice`</dt>
<dd>Отрицательные значения отсчитываются от конца строки:
```js
//+ run
alert( "testme".slice(-2) ); // "me", от 2 позиции с конца
```
```js
//+ run
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.
```
Это гораздо более удобно, чем странная логика `substring`.
</dd>
</dl>
Отрицательное значение первого параметра поддерживается в `substr` во всех браузерах, кроме IE8-.
Если выбирать из этих трёх методов один, для использования в большинстве ситуаций -- то это будет `slice`: он и отрицательные аргументы поддерживает и работает наиболее очевидно.
## Кодировка Юникод
Как мы знаем, символы сравниваются в алфавитном порядке `'А' < 'Б' < 'В' < ... < 'Я'`.
Но есть несколько странностей..
<ol>
<li>Почему буква `'а'` маленькая больше буквы `'Я'` большой?
```js
//+ run
alert( 'а' > 'Я' ); // true
```
</li><li>
Буква `'ё'` находится в алфавите между `е` и `ж`: <code>абвгде**ё**жз..</code>. Но почему тогда `'ё'` больше `'я'`?
```js
//+ run
alert( 'ё' > 'я' ); // true
```
</li>
</ol>
Чтобы разобраться с этим, обратимся к внутреннему представлению строк в JavaScript.
**Все строки имеют внутреннюю кодировку [Юникод](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4).**
Неважно, на каком языке написана страница, находится ли она в windows-1251 или utf-8. Внутри JavaScript-интерпретатора все строки приводятся к единому "юникодному" виду. Каждому символу соответствует свой код.
Есть метод для получения символа по его коду:
<dl>
<dt>String.fromCharCode(code)</dt>
<dd>Возвращает символ по коду `code`:
```js
//+ run
alert( String.fromCharCode(1072) ); // 'а'
```
</dd>
</dl>
...И метод для получения цифрового кода из символа:
<dl>
<dt>str.charCodeAt(pos)</dt>
<dd>Возвращает код символа на позиции `pos`. Отсчет позиции начинается с нуля.
```js
//+ run
alert( "абрикос".charCodeAt(0) ); // 1072, код 'а'
```
</dd>
</dl>
Теперь вернемся к примерам выше. Почему сравнения `'ё' > 'я'` и `'а' > 'Я'` дают такой странный результат?
Дело в том, что **символы сравниваются не по алфавиту, а по коду**. У кого код больше -- тот и больше. В юникоде есть много разных символов. Кириллическим буквам соответствует только небольшая часть из них, подробнее -- [Кириллица в Юникоде](http://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0_%D0%B2_%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4%D0%B5).
Выведем отрезок символов юникода с кодами от `1034` до `1113`:
```js
//+ run
var str = '';
for (var i = 1034; i <= 1113; i++) {
str += String.fromCharCode(i);
}
alert( str );
```
Результат:
<div style="overflow: auto">
<code>ЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљ</code>
</div>
Мы можем увидеть из этого отрезка две важных вещи:
<ol>
<li>**Строчные буквы идут после заглавных, поэтому они всегда больше.**
В частности, `'а'(код 1072) > 'Я'(код 1071)`.
То же самое происходит и в английском алфавите, там `'a' > 'Z'`.
</li>
<li>**Ряд букв, например `ё`, находятся вне основного алфавита.**
В частности, маленькая буква `ё` имеет код, больший чем `я`, поэтому **`'ё'(код 1105) > 'я'(код 1103)`**.
Кстати, большая буква `Ё` располагается в Unicode до `А`, поэтому **`'Ё'`(код 1025) < `'А'`(код 1040)**. Удивительно: есть буква меньше чем `А` :)
</li>
</ol>
**Буква `ё` не уникальна, точки над буквой используются и в других языках, приводя к тому же результату.**
Например, при работе с немецкими названиями:
```js
//+ run
alert( "ö" > "z" ); // true
```
[smart header="Юникод в HTML"]
Кстати, если мы знаем код символа в кодировке юникод, то можем добавить его в HTML, используя "числовую ссылку" (numeric character reference).
Для этого нужно написать сначала `&#`, затем код, и завершить точкой с запятой `';'`. Например, символ `'а'` в виде числовой ссылки: `&#1072;`.
Если код хотят дать в 16-ричной системе счисления, то начинают с `&#x`.
В юникоде есть много забавных и полезных символов, например, символ ножниц: &#x2702; (`&#x2702;`), дроби: &#xBD; (`&#xBD;`) &#xBE; (`&#xBE;`) и другие. Их можно использовать вместо картинок в дизайне.
[/smart]
## Посимвольное сравнение
Сравнение строк работает *лексикографически*, иначе говоря, посимвольно.
Сравнение строк `s1` и `s2` обрабатывается по следующему алгоритму:
<ol><li>Сравниваются первые символы: `s1[0]` и `s2[0]`. Если они разные, то сравниваем их и, в зависимости от результата их сравнения, возвратить `true` или `false`. Если же они одинаковые, то...</li>
<li>Сравниваются вторые символы `s1[1]` и `s2[1]`</li>
<li>Затем третьи `s1[2]` и `s2[2]` и так далее, пока символы не будут наконец разными, и тогда какой символ больше -- та строка и больше. Если же в какой-либо строке закончились символы, то считаем, что она меньше, а если закончились в обеих -- они равны.</li>
</ol>
Спецификация языка определяет этот алгоритм более детально. Если же говорить простыми словами, смысл алгоритма в точности соответствует порядку, по которому имена заносятся в орфографический словарь.
```js
"Вася" > "Ваня" // true, т.к. начальные символы совпадают, а потом 'с' > 'н'
"Дома" > "До" // true, т.к. начало совпадает, но в 1й строке больше символов
```
[warn header="Числа в виде строк сравниваются как строки"]
Бывает, что числа приходят в скрипт в виде строк, например как результат `prompt`. В этом случае результат их сравнения будет неверным:
```js
//+ run
alert( "2" > "14" ); // true, так как это строки, и для первых символов верно "2" > "1"
```
Если хотя бы один аргумент -- не строка, то другой будет преобразован к числу:
```js
//+ run
alert( 2 > "14" ); // false
```
[/warn]
## Правильное сравнение
Все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), поддерживающий сравнение строк на разных языках, с учётом их правил.
Способ использования:
```js
//+ run
var str = "Ёлки";
alert( str.localeCompare("Яблони") ); // -1
```
Метод `str1.localeCompare(str2)` возвращает `-1`, если `str1 < str2`, `1`, если `str1 > str2` и `0`, если они равны.
Более подробно про устройство этого метода можно будет узнать в статье [](/intl), когда это вам понадобится.
## Итого
<ul>
<li>Строки в JavaScript имеют внутреннюю кодировку Юникод. При написании строки можно использовать специальные символы, например `\n` и вставлять юникодные символы по коду.</li>
<li>Мы познакомились со свойством `length` и методами `charAt`, `toLowerCase/toUpperCase`, `substring/substr/slice` (предпочтителен `slice`). Есть и другие методы, например [trim](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) обрезает пробелы с начала и конца строки.</li>
<li>Строки сравниваются побуквенно. Поэтому если число получено в виде строки, то такие числа могут сравниваться некорректно, нужно преобразовать его к типу *number*.</li>
<li>При сравнении строк следует иметь в виду, что буквы сравниваются по их кодам. Поэтому большая буква меньше маленькой, а буква `ё` вообще вне основного алфавита.</li>
<li>Для правильного сравнения существует целый стандарт ECMA 402. Это не такое простое дело, много языков и много правил. Он поддерживается во всех современных браузерах, кроме IE10-, в которых нужна библиотека [](https://github.com/andyearnshaw/Intl.js/). Такое сравнение работает через вызов `str1.localeCompare(str2)`.</li>
</ul>
Больше информации о методах для строк можно получить в справочнике: [http://javascript.ru/String]().