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() только находит позицию первого совпадения.

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

Передача регулярного выражения без флага 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']

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

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) глобальный флаг не нужно явно указывать для поиска всех совпадений.

JS matchAll function comments

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