MatchAll: примеры (JAVASCRIPT)

Руководство по работе с методом matchAll в JavaScript
Раздел: Регулярные выражения, Поиск
matchAll(regexp): iterator

Базовая информация о методе matchAll()

Метод matchAll() применяется к строке и возвращает итератор по всем результатам сопоставления этой строки с указанным регулярным выражением. Каждый результат представляет собой массив с дополнительными свойствами index и input.

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

Единственный аргумент метода — объект регулярного выражения. Если передается строка, она автоматически преобразуется в RegExp. Важное требование: регулярное выражение должно иметь глобальный флаг g, иначе будет выброшена ошибка TypeError.

Возвращаемое значение — итератор (не массив). Каждый элемент итератора — массив, содержащий полное совпадение в виде первого элемента, а затем все группы захвата. Дополнительно каждый массив имеет свойства index (позиция совпадения) и input (исходная строка). Для работы с результатами как с массивом итератор можно преобразовать, используя Array.from() или оператор распространения (...).

Простые примеры использования

Поиск всех слов, начинающихся с заглавной буквы:

const text = 'JavaScript и Python — популярные языки.';
const regex = /\b[A-ZА-Я][a-zа-я]*\b/g;
const matches = [...text.matchAll(regex)];
console.log(matches.map(m => m[0]));
['JavaScript', 'Python']

Использование с группами захвата для извлечения данных:

const data = '10px, 20em, 30rem';
const regex = /(\d+)(px|em|rem)/g;
for (const match of data.matchAll(regex)) {
  console.log(`Значение: ${match[1]}, Единица: ${match[2]}, Позиция: ${match.index}`);
}
Значение: 10, Единица: px, Позиция: 0
Значение: 20, Единица: em, Позиция: 5
Значение: 30, Единица: rem, Позиция: 11

Похожие методы в JavaScript

String.match() возвращает массив совпадений, но без детальной информации о позициях и группах для каждого совпадения при глобальном поиске. Метод matchAll предпочтительнее, когда нужны группы захвата из всех совпадений.

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

String.split() с регулярным выражением может разделять строку, но не предназначен для извлечения групп.

String.search() только находит позицию первого совпадения.

Аналоги в других языках программирования

Python: Модуль re предоставляет методы finditer() и findall(). finditer() возвращает итератор объектов match, что наиболее близко к matchAll.

import re
text = '10px, 20em, 30rem'
pattern = re.compile(r'(\d+)(px|em|rem)')
for match in pattern.finditer(text):
    print(match.groups(), match.start())
('10', 'px') 0
('20', 'em') 5
('30', 'rem') 11

PHP: Функция preg_match_all() возвращает количество совпадений и заполняет массив результатами, включая группы.

$text = '10px, 20em, 30rem';
preg_match_all('/(\d+)(px|em|rem)/', $text, $matches, PREG_SET_ORDER);
print_r($matches);
Array ( [0] => Array ( [0] => 10px [1] => 10 [2] => px ) [1] => Array ... )

C#: Метод Matches() класса Regex возвращает коллекцию объектов Match, содержащих группы.

В отличие от JavaScript, во многих языках (Python, PHP) глобальный флаг не нужно явно указывать для поиска всех совпадений.

Распространенные ошибки

Передача регулярного выражения без флага g приводит к исключению.

const text = 'test';
try {
  [...text.matchAll(/test/)]; // Нет флага g
} catch (e) {
  console.log(e.name); // TypeError
}
TypeError

Попытка повторного использования итератора. Итератор, возвращаемый matchAll, является одноразовым.

const iterator = 'ab'.matchAll(/a/g);
console.log([...iterator]); // Первый проход
console.log([...iterator]); // Второй проход - пусто
[Array ['a']]
[]

Неправильное преобразование результата. Итератор напрямую не является массивом.

const result = '12'.matchAll(/\d/g);
console.log(result[0]); // undefined
// Правильно:
const array = [...result];
console.log(array[0][0]); // '1'
undefined
1

Изменения в спецификации

Метод String.prototype.matchAll был добавлен в стандарт ECMAScript 2020 (ES11). До его появления для получения аналогичного результата необходимо было использовать цикл с RegExp.exec(). Некоторые современные движки JavaScript начали поддерживать этот метод раньше официального принятия стандарта.

Расширенные примеры применения

Парсинг сложных шаблонов с именованными группами захвата:

Пример javascript
const log = 'ERROR [2023-10-05] File not found\nWARN [2023-10-06] Deprecated API';
const regex = /(?\w+)\s\[(?[^\]]+)\]\s(?.+)/g;
const logs = [];
for (const {groups} of log.matchAll(regex)) {
    logs.push(groups);
}
console.log(logs);
[
  { level: 'ERROR', date: '2023-10-05', message: 'File not found' },
  { level: 'WARN', date: '2023-10-06', message: 'Deprecated API' }
]

Поиск пересекающихся совпадений с использованием lookahead:

Пример javascript
const str = 'abc';
const regex = /(?=(.{2}))/g; // Ищем все позиции, за которыми следует 2 символа
const matches = [...str.matchAll(regex)].map(m => m[1]);
console.log(matches); // 'ab', 'bc'
['ab', 'bc']

Обработка многострочного текста с флагами g и m (multiline):

Пример javascript
const multilineText = `Строка 1
Строка 2
Строка 3`;
const regex = /^Строка\s(\d+)/gm;
const lines = [];
for (const match of multilineText.matchAll(regex)) {
    lines.push(`Номер строки в тексте: ${match[1]}, позиция: ${match.index}`);
}
console.log(lines);
['Номер строки в тексте: 1, позиция: 0',
 'Номер строки в тексте: 2, позиция: 9',
 'Номер строки в тексте: 3, позиция: 18']

Использование matchAll вместе с другими методами массивов для сложной фильтрации:

Пример javascript
const code = 'let x=5; let y=10; const z=15;';
const varRegex = /(let|const)\s+(\w+)\s*=/g;
const variables = [...code.matchAll(varRegex)]
    .filter(match => match[1] === 'let')
    .map(match => match[2]);
console.log(variables); // Только переменные, объявленные через let
['x', 'y']

JS matchAll function comments

En
MatchAll Returns an iterator of all results matching a string against a regular expression