Javascript RegExp не захватывающие группы

Я пишу набор RegExps для перевода селектора CSS в массивы идентификаторов и классов.

Например, мне бы хотелось, чтобы '# foo # bar' возвращался ['foo', 'bar'].

Я пытался добиться этого с помощью

"#foo#bar".match(/((?:#)[a-zA-Z0-9\-_]*)/g)

но он возвращает ['#foo', '#bar'], когда префикс без захвата?: должен игнорировать символ #.

Есть ли лучшее решение, чем нарезать каждую из возвращаемых строк?

Ответы

Ответ 1

Вы можете использовать .replace() или .exec() в цикле для создания массива.

С .replace():

var arr = [];
"#foo#bar".replace(/#([a-zA-Z0-9\-_]*)/g, function(s, g1) {
                                               arr.push(g1);
                                          });

С .exec():

var arr = [],
    s = "#foo#bar",
    re = /#([a-zA-Z0-9\-_]*)/g,
    item;

while (item = re.exec(s))
    arr.push(item[1]);

Ответ 2

Он соответствует #foo и #bar, потому что выполняется захват внешней группы (# 1). Внутренняя группа (# 2) не является, но это, вероятно, не то, что вы проверяете.

Если вы не использовали режим глобального сопоставления, немедленное исправление будет заключаться в использовании (/(?:#)([a-zA-Z0-9\-_]*)/.

В режиме глобального сопоставления результат не может быть получен только в одной строке, потому что match ведет себя по-другому. Используя только регулярное выражение (т.е. Никаких строковых операций), вам нужно сделать это следующим образом:

var re = /(?:#)([a-zA-Z0-9\-_]*)/g;
var matches = [], match;
while (match = re.exec("#foo#bar")) {
    matches.push(match[1]);
}

Посмотрите на действие.

Ответ 3

Я не уверен, что вы можете сделать это с помощью match(), но вы можете сделать это с помощью метода RegExp exec():

var pattern = new RegExp('#([a-zA-Z0-9\-_]+)', 'g');
var matches, ids = [];

while (matches = pattern.exec('#foo#bar')) {
    ids.push( matches[1] ); // -> 'foo' and then 'bar'
}

Ответ 4

К сожалению, в Javascript RegExp нет утверждения lookbehind, иначе вы могли бы сделать это:

/(?<=#)[a-zA-Z0-9\-_]*/g

Кроме того, что он добавляется в какую-то новую версию Javascript, я думаю, что использование пост-обработки split - ваш лучший выбор.

Ответ 5

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

"#foo#bar".match(/(?!#)[a-zA-Z0-9\-_]+/g);  // ["foo", "bar"]

Ответ 6

Утверждение за кадром, упомянутое несколько лет назад mVChr, добавлено в ECMAScript 2018. Это позволит вам сделать это:

'#foo#bar'.match(/(?<=#)[a-zA-Z0-9\-_]*/g) (возвращает ["foo", "bar"])

(Возможен также отрицательный взгляд назад: используйте (?<!#) Чтобы сопоставить любой символ, кроме #, без его захвата.)

Ответ 7

MDN документирует, что "группы захвата игнорируются при использовании match() с глобальным флагом /g", и рекомендует использовать matchAll(). matchAll() isn't available on Edge or Safari iOS, and you still need to skip the complete match (including the # ').

Более простое решение - отрезать начальный префикс, если вы знаете его длину - здесь, 1 для #.

const results = ('#foo#bar'.match(/#\w+/g) || []).map(s => s.slice(1));
console.log(results);