如何使用 JavaScript 中类似于 PHP 的 preg_match_all()的 regex 匹配多个匹配项?

我试图解析由键 = 值对组成的、由 &&分隔的 url 编码字符串。

下面将只匹配第一个匹配项,将键和值分解为单独的 result 元素:

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/)

字符串“1111342 = Adam% 20Franco & 348572 = Bob% 20Jones”的结果将是:

['1111342', 'Adam%20Franco']

使用全局标志‘ g’将匹配所有匹配项,但只返回完全匹配的子字符串,而不返回分隔的键和值:

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/g)

字符串“1111342 = Adam% 20Franco & 348572 = Bob% 20Jones”的结果将是:

['1111342=Adam%20Franco', '&348572=Bob%20Jones']

虽然我可以在 &上拆分字符串,并分解每个键/值对,但是有没有办法使用 JavaScript 的正则表达式支持来匹配多个类似于 PHP 的 preg_match_all()函数的模式 /(?:&|&)?([^=]+)=([^&]+)/

我的目标是找到一些方法来获得结果,将子匹配分开,比如:

[['1111342', '348572'], ['Adam%20Franco', 'Bob%20Jones']]

或者

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]
265983 次浏览

为全局匹配设置 g修饰符:

/…/g

您需要使用“ g”开关进行全局搜索

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)

从评论中提起

2020注释: 我们现在使用的是 URLSearchParams,而不是使用正则表达式,它为我们完成了所有这些任务,因此不再需要自定义代码,更不用说正则表达式了。

Mike“ Pomax”Kamermans

这里列出了浏览器支持 https://caniuse.com/#feat=urlsearchparams


我建议使用另一种 regex,使用子组分别捕获参数的名称和值以及 re.exec():

function getUrlParams(url) {
var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
match, params = {},
decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};


if (typeof url == "undefined") url = document.location.href;


while (match = re.exec(url)) {
params[decode(match[1])] = decode(match[2]);
}
return params;
}


var result = getUrlParams("http://maps.google.de/maps?f=q&source=s_q&hl=de&geocode=&q=Frankfurt+am+Main&sll=50.106047,8.679886&sspn=0.370369,0.833588&ie=UTF8&ll=50.116616,8.680573&spn=0.35972,0.833588&z=11&iwloc=addr");

result是一个对象:

{
f: "q"
geocode: ""
hl: "de"
ie: "UTF8"
iwloc: "addr"
ll: "50.116616,8.680573"
q: "Frankfurt am Main"
sll: "50.106047,8.679886"
source: "s_q"
spn: "0.35972,0.833588"
sspn: "0.370369,0.833588"
z: "11"
}

正则表达式分类如下:

(?:            # non-capturing group
\?|&         #   "?" or "&"
(?:amp;)?    #   (allow "&", for wrongly HTML-encoded URLs)
)              # end non-capturing group
(              # group 1
[^=&#]+      #   any character except "=", "&" or "#"; at least once
)              # end group 1 - this will be the parameter's name
(?:            # non-capturing group
=?           #   an "=", optional
(            #   group 2
[^&#]*     #     any character except "&" or "#"; any number of times
)            #   end group 2 - this will be the parameter's value
)              # end non-capturing group

对于捕获组,我习惯于在 PHP 中使用 preg_match_all,并试图在这里复制它的功能:

<script>


// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
var match = null;
var matches = new Array();
while (match = this.exec(string)) {
var matchArray = [];
for (i in match) {
if (parseInt(i) == i) {
matchArray.push(match[i]);
}
}
matches.push(matchArray);
}
return matches;
}


// Example
var someTxt = 'abc123 def456 ghi890';
var results = /[a-z]+(\d+)/g.execAll(someTxt);


// Output
[["abc123", "123"],
["def456", "456"],
["ghi890", "890"]]


</script>

我也有过类似的问题。 我希望使用 RegExp 进行增量/步骤搜索 (例如: 开始搜索... 做一些处理... 继续搜索,直到最后一场比赛)

经过大量的互联网搜索... 像往常一样(这正在转变成一种习惯) 我在 StackOverflow 找到了答案。

没有提到的和要提到的是“ lastIndex” 现在我明白了为什么 RegExp 对象实现“ lastIndex”属性

为了捕获几个同名的参数,我在 Tomalak 的方法中修改了 while 循环,如下所示:

  while (match = re.exec(url)) {
var pName = decode(match[1]);
var pValue = decode(match[2]);
params[pName] ? params[pName].push(pValue) : params[pName] = [pValue];
}

输入: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

报税表: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}

如果有人(比如我)需要支持数组的 Tomalak 方法(例如,多重选择) ,这里是:

function getUrlParams(url) {
var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
match, params = {},
decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};


if (typeof url == "undefined") url = document.location.href;


while (match = re.exec(url)) {
if( params[decode(match[1])] ) {
if( typeof params[decode(match[1])] != 'object' ) {
params[decode(match[1])] = new Array( params[decode(match[1])], decode(match[2]) );
} else {
params[decode(match[1])].push(decode(match[2]));
}
}
else
params[decode(match[1])] = decode(match[2]);
}
return params;
}
var urlParams = getUrlParams(location.search);

输入 ?my=1&my=2&my=things

结果 1,2,things(早期仅返回: things)

2020年编辑

使用 URLSearchParams,因为此作业不再需要任何类型的自定义代码。浏览器可以通过一个构造函数来完成这项工作:

const str = "1111342=Adam%20Franco&348572=Bob%20Jones";
const data = new URLSearchParams(str);
for (pair of data) console.log(pair)

产量

Array [ "1111342", "Adam Franco" ]
Array [ "348572", "Bob Jones" ]

因此再也没有理由使用正则表达式了。

原始答案

如果你不想依赖于运行 exec样式匹配所带来的“盲匹配”,JavaScript 确实提供了内置的所有匹配功能,但它是 replace函数调用的一部分,当使用“如何处理捕获组”时,它是 操作功能:

var data = {};


var getKeyValue = function(fullPattern, group1, group2, group3) {
data[group2] = group3;
};


mystring.replace(/(?:&|&amp;)?([^=]+)=([^&]+)/g, getKeyValue);

搞定。

不使用捕获组处理函数实际返回替换字符串(对于替换处理,第一个参数是完全模式匹配,后续的参数是单个捕获组) ,我们只获取组2和组3捕获,并缓存该对。

因此,与其编写复杂的解析函数,不如记住 JavaScript 中的“ match all”函数只是用一个替换处理函数“替换”,这样就可以获得更高的模式匹配效率。

只要坚持使用标题中提出的问题,您实际上可以使用 String.prototype.replace()在字符串中迭代每个匹配项。例如,下面的操作就是根据正则表达式获取所有单词的数组:

function getWords(str) {
var arr = [];
str.replace(/\w+/g, function(m) {
arr.push(m);
});
return arr;
}


var words = getWords("Where in the world is Carmen Sandiego?");
// > ["Where", "in", "the", "world", "is", "Carmen", "Sandiego"]

如果我想得到捕获组,甚至每个匹配的索引,我也可以这样做。下面显示了如何使用整个匹配、第一个捕获组和索引返回每个匹配:

function getWords(str) {
var arr = [];
str.replace(/\w+(?=(.*))/g, function(m, remaining, index) {
arr.push({ match: m, remainder: remaining, index: index });
});
return arr;
}


var words = getWords("Where in the world is Carmen Sandiego?");

运行上述程序后,words将如下:

[
{
"match": "Where",
"remainder": " in the world is Carmen Sandiego?",
"index": 0
},
{
"match": "in",
"remainder": " the world is Carmen Sandiego?",
"index": 6
},
{
"match": "the",
"remainder": " world is Carmen Sandiego?",
"index": 9
},
{
"match": "world",
"remainder": " is Carmen Sandiego?",
"index": 13
},
{
"match": "is",
"remainder": " Carmen Sandiego?",
"index": 19
},
{
"match": "Carmen",
"remainder": " Sandiego?",
"index": 22
},
{
"match": "Sandiego",
"remainder": "?",
"index": 29
}
]

为了匹配多个类似 PHP 中使用 preg_match_all的事件,您可以使用这种类型的思维来创建自己的事件或使用类似于 YourJS.matchAll()的事件。YourJS 或多或少将这一功能定义如下:

function matchAll(str, rgx) {
var arr, extras, matches = [];
str.replace(rgx.global ? rgx : new RegExp(rgx.source, (rgx + '').replace(/[\s\S]+\//g , 'g')), function() {
matches.push(arr = [].slice.call(arguments));
extras = arr.splice(-2);
arr.index = extras[0];
arr.input = extras[1];
});
return matches[0] ? matches : null;
}

来源:
Https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/regexp/exec

找到连续的匹配

如果正则表达式使用“ g”标志,则可以多次使用 exec ()方法在同一个字符串中查找连续的匹配。当您这样做时,搜索从正则表达式的 lastIndex 属性指定的 str 的子字符串开始(test ()也将推进 lastIndex 属性)。例如,假设您有以下脚本:

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
var msg = 'Found ' + myArray[0] + '. ';
msg += 'Next match starts at ' + myRe.lastIndex;
console.log(msg);
}

此脚本显示以下文本:

Found abb. Next match starts at 3
Found ab. Next match starts at 912

注意: 不要将正则表达式文字(或 RegExp 构造函数)放在 while 条件中,否则,如果在每次迭代时由于 lastIndex 属性被重置而出现匹配,那么它将创建一个无限循环。还要确保设置了全局标志,否则这里也会出现一个循环。

如果你可以使用 map,这是一个四行的解决方案:

var mystring = '1111342=Adam%20Franco&348572=Bob%20Jones';


var result = mystring.match(/(&|&amp;)?([^=]+)=([^&]+)/g) || [];
result = result.map(function(i) {
return i.match(/(&|&amp;)?([^=]+)=([^&]+)/);
});


console.log(result);

不漂亮,不高效,但至少很紧凑。 ;)

使用 window.URL:

> s = 'http://www.example.com/index.html?1111342=Adam%20Franco&348572=Bob%20Jones'
> u = new URL(s)
> Array.from(u.searchParams.entries())
[["1111342", "Adam Franco"], ["348572", "Bob Jones"]]

对我来说,分摊似乎是最好的选择:

'1111342=Adam%20Franco&348572=Bob%20Jones'.split('&').map(x => x.match(/(?:&|&amp;)?([^=]+)=([^&]+)/))

为了避免正则表达式地狱,您可以找到您的第一个匹配,删除一个块,然后尝试在子字符串中找到下一个。在 C # 中,这看起来是这样的,对不起,我还没有把它移植到 JavaScript 中。

        long count = 0;
var remainder = data;
Match match = null;
do
{
match = _rgx.Match(remainder);
if (match.Success)
{
count++;
remainder = remainder.Substring(match.Index + 1, remainder.Length - (match.Index+1));
}
} while (match.Success);
return count;

你好,我是2020年的,请注意 Prototype.match All ():

let regexp = /(?:&|&amp;)?([^=]+)=([^&]+)/g;
let str = '1111342=Adam%20Franco&348572=Bob%20Jones';


for (let match of str.matchAll(regexp)) {
let [full, key, value] = match;
console.log(key + ' => ' + value);
}

产出:

1111342 => Adam%20Franco
348572 => Bob%20Jones