en.javascript.info/11-regular-expressions-javascript/2-regexp-methods/article.md
2015-03-24 00:03:51 +03:00

392 lines
17 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.

# Методы RegExp и String
Регулярные выражения в JavaScript являются объектами класса [RegExp](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
Кроме того, методы для поиска по регулярным выражениям встроены прямо в обычные строки `String`.
К сожалению, общая структура встроенных методов слегка запутана, поэтому мы сначала рассмотрим их по отдельности, а затем -- рецепты по решению стандартных задач с ними.
[cut]
## str.search(reg)
Этот метод мы уже видели.
Он возвращает позицию первого совпадения или `-1`, если ничего не найдено.
```js
//+ run
var str = "Люблю регэкспы я, но странною любовью";
alert( str.search( *!*/лю/i*/!* ) ); // 0
```
**Ограничение метода `search` -- он всегда ищет только первое совпадение.**
Нельзя заставить `search` искать дальше первого совпадения, такой синтаксис попросту не предусмотрен. Но есть другие методы, которые это умеют.
## str.match(reg) без флага g
Метод `str.match` работает по-разному, в зависимости от наличия или отсутствия флага `g`, поэтому сначала мы разберём вариант, когда его нет.
В этом случае `str.match(reg)` находит только одно, первое совпадение.
Результат вызова -- это массив, состоящий из этого совпадения, с дополнительными свойствами `index` -- позиция, на которой оно обнаружено и `input` -- строка, в которой был поиск.
Например:
```js
//+ run
var str = "ОЙ-Ой-ой";
var result = str.match( *!*/ой/i*/!* );
alert( result[0] ); // ОЙ (совпадение)
alert( result.index ); // 0 (позиция)
alert( result.input ); // ОЙ-Ой-ой (вся поисковая строка)
```
У этого массива не всегда только один элемент.
**Если часть шаблона обозначена скобками, то она станет отдельным элементом массива.**
Например:
```js
//+ run
var str = "javascript - это такой язык";
var result = str.match( *!*/JAVA(SCRIPT)/i*/!* );
alert( result[0] ); // javascript (всё совпадение полностью)
alert( result[1] ); // script (часть совпадения, соответствующая скобкам)
alert( result.index ); // 0
alert( result.input ); // javascript - это такой язык
```
Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит <code class="match">javascript</code>. При этом часть строки, соответствующая <code class="pattern">SCRIPT</code>, выделена в отдельный элемент массива.
Позже мы ещё вернёмся к скобочным выражениям, они особенно удобны для поиска с заменой.
## str.match(reg) с флагом g
При наличии флага `g`, вызов `match` возвращает обычный массив из всех совпадений.
Никаких дополнительных свойств у массива в этом случае нет, скобки дополнительных элементов не порождают.
Например:
```js
//+ run
var str = "ОЙ-Ой-ой";
var result = str.match( *!*/ой/ig*/!* );
alert( result ); // ОЙ, Ой, ой
```
Пример со скобками:
```js
//+ run
var str = "javascript - это такой язык";
var result = str.match( *!*/JAVA(SCRIPT)/gi*/!* );
alert( result[0] ); // javascript
alert( result.length ); // 1
alert( result.index ); // undefined
```
Из последнего примера видно, что элемент в массиве ровно один, и свойства `index` также нет. Такова особенность глобального поиска при помощи `match` -- он просто возвращает все совпадения.
Для расширенного глобального поиска, который позволит получить все позиции и, при желании, скобки, нужно использовать метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec), которые будет рассмотрен далее.
[warn header="В случае, если совпадений не было, `match` возвращает `null`"]
Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`.
Это важно иметь в виду, чтобы не попасть в такую ловушку:
```js
//+ run
var str = "Ой-йой-йой";
// результат match не всегда массив!
alert(str.match(/лю/gi).length) // ошибка! нет свойства length у null
```
[/warn]
## str.split(reg|substr, limit)
Разбивает строку в массив по разделителю -- регулярному выражению `regexp` или подстроке `substr`.
Обычно мы используем метод `split` со строками, вот так:
```js
//+ run
alert('12-34-56'.split('-')) // [12, 34, 56]
```
Можно передать в него и регулярное выражение, тогда он разобьёт строку по всем совпадениям.
Тот же пример с регэкспом:
```js
//+ run
alert('12-34-56'.split(/-/)) // [12, 34, 56]
```
## str.replace(reg, str|func)
Швейцарский нож для работы со строками, поиска и замены любого уровня сложности.
Его простейшее применение -- поиск и замена подстроки в строке, вот так:
```js
//+ run
// заменить дефис на двоеточие
alert('12-34-56'.replace("-", ":")) // 12:34-56
```
**При вызове со строкой замены `replace` всегда заменяет только первое совпадение.**
Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение <code class="pattern">/-/g</code>, причём обязательно с флагом `g`:
```js
//+ run
// заменить дефис на двоеточие
alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56
```
В строке для замены можно использовать специальные символы:
<table>
<thead>
<tr>
<th>Спецсимволы</th>
<th>Действие в строке замены</th>
</tr>
</thead>
<tbody>
<tr>
<td>`$$`</td>
<td>Вставляет `"$"`.</td>
</tr>
<tr>
<td>`$&`</td>
<td>Вставляет всё найденное совпадение.</td>
</tr>
<tr>
<td><code>$&#096;</code></td>
<td>Вставляет часть строки до совпадения.</td>
</tr>
<tr>
<td>
<code>$'</code>
</td>
<td>Вставляет часть строки после совпадения.</td>
</tr>
<tr>
<td>
<code>$*n*</code>
</td>
<td>где `n` -- цифра или двузначное число, обозначает `n-ю` по счёту скобку, если считать слева-направо.</td>
</tr>
</tbody>
</table>
Пример использования скобок и `$1`, `$2`:
```js
//+ run
var str = "Василий Пупкин";
alert(str.replace(/(Василий) (Пупкин)/, '$2, $1')) // Пупкин, Василий
```
Ещё пример, с использованием `$&`:
```js
//+ run
var str = "Василий Пупкин";
alert(str.replace(/Василий Пупкин/, 'Великий $&!')) // Великий Василий Пупкин!
```
**Для ситуаций, который требуют максимально "умной" замены, в качестве второго аргумента предусмотрена функция.**
Она будет вызвана для каждого совпадения, и её результат будет вставлен как замена.
Например:
```js
//+ run
var i = 0;
// заменить каждое вхождение "ой" на результат вызова функции
alert("ОЙ-Ой-ой".replace(/ой/gi, function() {
return ++i;
})); // 1-2-3
```
В примере выше функция просто возвращала числа по очереди, но обычно она основывается на поисковых данных.
Эта функция получает следующие аргументы:
<ol>
<li>`str` -- найденное совпадение,</li>
<li>`p1, p2, ..., pn` -- содержимое скобок (если есть),</li>
<li>`offset` -- позиция, на которой найдено совпадение,</li>
<li>`s` -- исходная строка.</li>
</ol>
Если скобок в регулярном выражении нет, то у функции всегда будет ровно 3 аргумента: `replacer(str, offset, s)`.
Используем это, чтобы вывести полную информацию о совпадениях:
```js
//+ run
// вывести и заменить все совпадения
function replacer(str, offset, s) {
alert( "Найдено: " + str + " на позиции: " + offset + " в строке: " + s );
return str.toLowerCase();
}
var result = "ОЙ-Ой-ой".replace(/ой/gi, replacer);
alert( 'Результат: ' + result ); // Результат: ой-ой-ой
```
С двумя скобочными выражениями -- аргументов уже 5:
```js
//+ run
function replacer(str, name, surname, offset, s) {
return surname + ", " + name;
}
alert(str.replace(/(Василий) (Пупкин)/, replacer)) // Пупкин, Василий
```
Функция -- это самый мощный инструмент для замены, какой только может быть. Она владеет всей информацией о совпадении и имеет доступ к замыканию, поэтому может всё.
## regexp.test(str)
Теперь переходим к методам класса `RegExp`.
Метод `test` проверяет, есть ли хоть одно совпадение в строке `str`. Возвращает `true/false`.
Работает, по сути, так же, как и проверка `str.search(reg) != -1`, например:
```js
//+ run
var str = "Люблю регэкспы я, но странною любовью";
// эти две проверки идентичны
alert( *!*/лю/i*/!*.test(str) ) // true
alert( str.search(*!*/лю/i*/!*) != -1 ) // true
```
Пример с отрицательным результатом:
```js
//+ run
var str = "Ой, цветёт калина...";
alert( *!*/javascript/i*/!*.test(str) ) // false
alert( str.search(*!*/javascript/i*/!*) != -1 ) // false
```
## regexp.exec(str)
Для поиска мы уже видели методы:
<ul>
<li>`search` -- ищет индекс</li>
<li>`match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках</li>
<li>`match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп.</li>
</ul>
Метод `regexp.exec` дополняет их. Он позволяет искать и все совпадения и скобочные группы в них.
Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`.
<ul>
<li>Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`.</li>
<li>Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль.</li>
</ul>
Это используют для поиска всех совпадений в цикле:
```js
//+ run
var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru';
var regexp = /javascript/ig;
alert( "Начальное значение lastIndex: " + regexp.lastIndex );
while (result = regexp.exec(str)) {
alert( 'Найдено: ' + result[0] + ' на позиции:' + result.index );
alert( 'Свойство lastIndex: ' + regexp.lastIndex );
}
alert( 'Конечное значение lastIndex: ' + regexp.lastIndex );
```
Здесь цикл продолжается до тех пор, пока `regexp.exec` не вернёт `null`, что означает "совпадений больше нет".
Найденные результаты последовательно помещаются в `result`, причём находятся там в том же формате, что и `match` -- с учётом скобок, со свойствами `result.index` и `result.input`.
[smart header="Поиск с нужной позиции"]
Можно заставить `regexp.exec` искать сразу с нужной позиции, если поставить `lastIndex` вручную:
```js
//+ run
var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru';
var regexp = /javascript/ig;
regexp.lastIndex = 40;
alert( regexp.exec(str).index ); // 49, поиск начат с 40й позиции
```
[/smart]
## Итого, рецепты
Методы становятся гораздо понятнее, если разбить их использование по задачам, которые нужны в реальной жизни.
<dl>
<dt>Для поиска только одного совпадения:</dt>
<dd>
<ul>
<li>Найти позицию первого совпадения -- `str.search(reg)`.</li>
<li>Найти само совпадение -- `str.match(reg)`.</li>
<li>Проверить, есть ли хоть одно совпадение -- `regexp.test(str)` или `str.search(reg) != -1`.</li>
<li>Найти совпадение с нужной позиции -- `regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`.</li>
</ul>
</dd>
<dt>Для поиска всех совпадений:</dt>
<dd>
<ul>
<li>Найти массив совпадений -- `str.match(reg)`, с флагом `g`.</li>
<li>Получить все совпадения, с подробной информацией о каждом -- `regexp.exec(str)` с флагом `g`, в цикле.</li>
</ul>
</dd>
</dt>
<dt>Для поиска-и-замены:</dt>
<dd>
<ul>
<li>Замена на другую строку или функцией -- `str.replace(reg, str|func)`</li>
</ul>
</dd>
<dt>Для разбивки строки на части:</dt>
<dd>
<ul>
<li>`str.split(str|reg)`</li>
</ul>
</dd>
</dl>
Зная эти методы, мы уже можем использовать регулярные выражения.
Конечно, для этого желательно хорошо понимать их синтаксис и возможности, так что переходим к ним дальше.