【发布时间】:2012-06-29 23:52:11
【问题描述】:
当我跑步时
/(a)/g.exec('a a a ').length
我明白了
2
但我认为它应该返回
3
因为字符串中有 3 个as,而不是 2 个!
这是为什么呢?
我希望能够在 RegEx 中搜索所有出现的字符串并对其进行迭代。
FWIW:我正在使用 node.js
【问题讨论】:
标签: javascript regex node.js v8
当我跑步时
/(a)/g.exec('a a a ').length
我明白了
2
但我认为它应该返回
3
因为字符串中有 3 个as,而不是 2 个!
这是为什么呢?
我希望能够在 RegEx 中搜索所有出现的字符串并对其进行迭代。
FWIW:我正在使用 node.js
【问题讨论】:
标签: javascript regex node.js v8
exec() 仅返回第一个匹配的捕获集,而不是您期望的匹配集。所以你真正看到的是$0(整个比赛,“a”)和$1(第一次捕获)——即一个长度为 2 的数组。exec() 同时被设计为您可以调用它 again 来获取 next 匹配的捕获。来自MDN:
如果您的正则表达式使用“g”标志,您可以多次使用 exec 方法在同一字符串中查找连续匹配项。当您这样做时,搜索将从正则表达式的 lastIndex 属性指定的 str 的子字符串开始(测试也将推进 lastIndex 属性)。
【讨论】:
$&。
$变量。我只是在用 Perl 说话。 :) 不过很高兴知道。
您可以改用match:
'a a a'.match(/(a)/g).length // outputs: 3
【讨论】:
match 是这种情况下的理想工具,前提是 OP 不需要每次匹配中的子组。
match 返回 null 而不是空数组。
while 循环可以帮助你
x = 'a a a a';
y = new RegExp(/a/g);
while(null != (z=y.exec(x))) {
console.log(z); // output: object
console.log(z[0]); // ouput: "a"
}
如果你添加计数器,那么你会得到它的长度。
x = 'a a a a';
counter = 0;
y = new RegExp(/a/g);
while(null != (z=y.exec(x))) {
console.log(z); // output: object
console.log(z[0]); // output: "a"
counter++;
}
console.log(counter); // output: 4
这是相当安全的,即使它没有找到任何匹配的,它也会退出并且计数器将为 0
主要目的是说明如何使用 RegExp 循环并从相同匹配的 RegExp 字符串中获取所有值
【讨论】:
.exec 返回一些.match 没有返回的额外属性,它意味着被迭代调用以返回每个增量匹配。仅供参考,您可以从 while 循环中删除 null != ,因为 null 值将是虚假的并退出循环。另外,快乐赏金宾果游戏!
null !=。但我觉得这对少数新手了解代码流会有帮助。
'g' 标志(全局)的 RegExp 尝试了这个。不使用会产生无限循环。
'g' 标志将进入无限循环,因为每次搜索指针都会重新指向起点 0。
y = /a/g 就足够了,因为它已经是一个正则表达式。调用new RegExp() 是多余的。
您只匹配第一个 a。长度为 2 的原因是它正在查找第一个匹配项和第一个匹配项的括号组部分。在您的情况下,它们是相同的。
考虑这个例子。
var a = /b(a)/g.exec('ba ba ba ');
alert(a);
它输出ba, a。数组长度仍然是 2,但更明显的是发生了什么。 "ba" 是完全匹配。 a 是括号内的第一个分组匹配。
MDN documentation 支持这一点 - 仅返回第一个匹配项和包含的组。要查找所有匹配项,您可以使用 mVChr 中所述的 match()。
【讨论】:
代码:
alert('a a a'.match(/(a)/g).length);
输出:
3
【讨论】:
regexp.exec(str) 返回第一个匹配项或整个匹配项和第一个捕获(当 re = /(a)/g; 时),如其他答案中所述
const str = 'a a a a a a a a a a a a a';
const re = /a/g;
const result = re.exec(str);
console.log(result);
但它也记住它在regexp.lastIndex 属性中的位置。
下一个调用从regexp.lastIndex开始搜索并返回下一个匹配项。
如果没有更多匹配项,则 regexp.exec 返回 null 并将 regexp.lastIndex 设置为 0。
const str = 'a a a';
const re = /a/g;
const a = re.exec(str);
console.log('match : ', a, ' found at : ', re.lastIndex);
const b = re.exec(str);
console.log('match : ', b, ' found at : ', re.lastIndex);
const c = re.exec(str);
console.log('match : ', c, ' found at : ', re.lastIndex);
const d = re.exec(str);
console.log('match : ', d, ' found at : ', re.lastIndex);
const e = re.exec(str);
console.log('match : ', e, ' found at : ', re.lastIndex);
这就是为什么您可以使用当匹配为null 时停止的while 循环
const str = 'a a a';
const re = /a/g;
while(match = re.exec(str)){
console.log(match, ' found at : ', match.index);
}
【讨论】:
对于您的示例,.match() 是您的最佳选择。但是,如果您确实需要子组,您可以创建一个生成器函数。
function* execAll(str, regex) {
if (!regex.global) {
console.error('RegExp must have the global flag to retrieve multiple results.');
}
let match;
while (match = regex.exec(str)) {
yield match;
}
}
const matches = execAll('a abbbbb no match ab', /\b(a)(b+)?\b/g);
for (const match of matches) {
console.log(JSON.stringify(match));
let otherProps = {};
for (const [key, value] of Object.entries(match)) {
if (isNaN(Number(key))) {
otherProps[key] = value;
}
}
console.log(otherProps);
}
虽然大多数 JS 程序员认为污染原型是不好的做法,但您也可以将其添加到 RegExp.prototype。
if (RegExp.prototype.hasOwnProperty('execAll')) {
console.error('RegExp prototype already includes a value for execAll. Not overwriting it.');
} else {
RegExp.prototype.execAll =
RegExp.prototype = function* execAll(str) {
if (!this.global) {
console.error('RegExp must have the global flag to retrieve multiple results.');
}
let match;
while (match = this.exec(str)) {
yield match;
}
};
}
const matches = /\b(a)(b+)?\b/g.execAll('a abbbbb no match ab');
console.log(Array.from(matches));
【讨论】:
已经有几个答案,但不必要地复杂。对结果的身份检查过多,因为它始终是数组或null。
let text = `How much wood would a woodchuck chuck if a woodchuck could chuck wood?`;
let re = /wood/g;
let lastMatch;
while (lastMatch = re.exec(text)) {
console.log(lastMatch);
console.log(re.lastIndex);
// Avoid infinite loop
if(!re.global) break;
}
您可以将无限循环保护移到条件表达式中。
while (re.global && (lastMatch = re.exec(text))) {
console.log(lastMatch);
console.log(re.lastIndex);
}
【讨论】:
封装成实用函数:
const regexExecAll = (str: string, regex: RegExp) => {
let lastMatch: RegExpExecArray | null;
const matches: RegExpExecArray[] = [];
while ((lastMatch = regex.exec(str))) {
matches.push(lastMatch);
if (!regex.global) break;
}
return matches;
};
用法:
const matches = regexExecAll("a a a", /(a)/g);
console.log(matches);
输出:
[
[ 'a', 'a', index: 0, input: 'a a a', groups: undefined ],
[ 'a', 'a', index: 2, input: 'a a a', groups: undefined ],
[ 'a', 'a', index: 4, input: 'a a a', groups: undefined ]
]
【讨论】: