392 lines
17 KiB
Markdown
392 lines
17 KiB
Markdown
# Методы 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>$`</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>
|
||
|
||
Зная эти методы, мы уже можем использовать регулярные выражения.
|
||
|
||
Конечно, для этого желательно хорошо понимать их синтаксис и возможности, так что переходим к ним дальше.
|