Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
353 lines (235 sloc) 16.3 KB

Методы RegExp и String

Регулярные выражения в JavaScript являются объектами класса RegExp.

Кроме того, методы для поиска по регулярным выражениям встроены прямо в обычные строки String.

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

str.search(reg)

Этот метод мы уже видели.

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

var str = "Люблю регэкспы я, но странною любовью";

alert( str.search( *!*/лю/i*/!* ) ); // 0

Ограничение метода search -- он всегда ищет только первое совпадение.

Нельзя заставить search искать дальше первого совпадения, такой синтаксис попросту не предусмотрен. Но есть другие методы, которые это умеют.

str.match(reg) без флага g

Метод str.match работает по-разному, в зависимости от наличия или отсутствия флага g, поэтому сначала мы разберём вариант, когда его нет.

В этом случае str.match(reg) находит только одно, первое совпадение.

Результат вызова -- это массив, состоящий из этого совпадения, с дополнительными свойствами index -- позиция, на которой оно обнаружено и input -- строка, в которой был поиск.

Например:

var str = "ОЙ-Ой-ой";

var result = str.match( *!*/ой/i*/!* );

alert( result[0] ); // ОЙ  (совпадение)
alert( result.index ); // 0 (позиция)
alert( result.input ); // ОЙ-Ой-ой (вся поисковая строка)

У этого массива не всегда только один элемент.

Если часть шаблона обозначена скобками, то она станет отдельным элементом массива.

Например:

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 поиск не обращает внимание на регистр буквы, поэтому находит match:javascript. При этом часть строки, соответствующая pattern:SCRIPT, выделена в отдельный элемент массива.

Позже мы ещё вернёмся к скобочным выражениям, они особенно удобны для поиска с заменой.

str.match(reg) с флагом g

При наличии флага g, вызов match возвращает обычный массив из всех совпадений.

Никаких дополнительных свойств у массива в этом случае нет, скобки дополнительных элементов не порождают.

Например:

var str = "ОЙ-Ой-ой";

var result = str.match( *!*/ой/ig*/!* );

alert( result ); // ОЙ, Ой, ой

Пример со скобками:

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, которые будет рассмотрен далее.

````warn header="В случае, если совпадений не было, match возвращает `null`" Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`.

Это важно иметь в виду, чтобы не попасть в такую ловушку:

var str = "Ой-йой-йой";

// результат match не всегда массив!
alert(str.match(/лю/gi).length) // ошибка! нет свойства length у null

## 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` всегда заменяет только первое совпадение.**

Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение `pattern:/-/g`, причём обязательно с флагом `g`:

```js run
// заменить дефис на двоеточие
alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) )  // 12:34:56
```

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

<table>
    <thead>
    <tr>
        <th>Спецсимволы</th>
        <th>Действие в строке замены</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td><code>$$</code></td>
        <td>Вставляет <code>"$"</code>.</td>
    </tr>
    <tr>
        <td><code>$&</code></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>где <code>n</code> -- цифра или двузначное число, обозначает <code>n-ю</code> по счёту скобку, если считать слева-направо.</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
```

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

Эта функция получает следующие аргументы:

1. `str` -- найденное совпадение,
2. `p1, p2, ..., pn` -- содержимое скобок (если есть),
3. `offset` -- позиция, на которой найдено совпадение,
4. `s` -- исходная строка.

Если скобок в регулярном выражении нет, то у функции всегда будет ровно 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;
}

var str = "Василий Пупкин";

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)

Для поиска мы уже видели методы:

- `search` -- ищет индекс
- `match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках
- `match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп.

Метод `regexp.exec` дополняет их. Он позволяет искать и все совпадения и скобочные группы в них.

Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`.

- Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`.
- Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль.

Это используют для поиска всех совпадений в цикле:

```js run
var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru';

var regexp = /javascript/ig;
var result;

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-й позиции
```

Итого, рецепты

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

Для поиска только одного совпадения: : - Найти позицию первого совпадения -- str.search(reg).

  • Найти само совпадение -- str.match(reg).
  • Проверить, есть ли хоть одно совпадение -- regexp.test(str) или str.search(reg) != -1.
  • Найти совпадение с нужной позиции -- regexp.exec(str), начальную позицию поиска задать в regexp.lastIndex.

Для поиска всех совпадений: : - Найти массив совпадений -- str.match(reg), с флагом g.

  • Получить все совпадения, с подробной информацией о каждом -- regexp.exec(str) с флагом g, в цикле.

Для поиска-и-замены: : - Замена на другую строку или результат функции -- str.replace(reg, str|func)

Для разбивки строки на части: : - str.split(str|reg)

Зная эти методы, мы уже можем использовать регулярные выражения.

Конечно, для этого желательно хорошо понимать их синтаксис и возможности, так что переходим к ним дальше.