如何在JavaScript中使用类似于PHP的preg_match_all()的正则表达式匹配多个匹配项?


160

我正在尝试解析由以key &或value分隔的key = value对组成的url编码的字符串&

以下将仅匹配第一个匹配项,将键和值拆分为单独的结果元素:

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']]

9
没人建议replace在这里使用,这有点奇怪。var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });完成。JavaScript中的“ matchAll”被替换处理函数而不是字符串“替换”。
Mike'Pomax'Kamermans 2014年

请注意,对于那些到2020年仍然发现此问题的人,答案是“不要使用正则表达式,请使用URLSearchParams,这将为您完成所有这些工作”。
Mike'Pomax'Kamermans

Answers:


161

从评论中悬挂

2020年评论:现在,我们不再使用regex,而是URLSearchParams为我们完成了所有这些工作,因此不再需要自定义代码,更不用说regex了。

Mike'Pomax'Kamermans

浏览器支持在此处列出https://caniuse.com/#feat=urlsearchparams


我建议使用另一种正则表达式,使用子组分别捕获参数的名称和值以及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”
  地理编码:“”
  hl:“ de”
  即:“ UTF8”
  iwloc:“地址”
  ll:“ 50.116616,8.680573”
  问:“美因河畔法兰克福”
  sll:“ 50.106047,8.679886”
  来源:“ s_q”
  spn:“ 0.35972,0.833588”
  sspn:“ 0.370369,0.833588”
  z:“ 11”
}

正则表达式分解如下:

(?:#非捕获组
  \?|&#“?” 要么 ”&”
  (?:amp;)?#(允许“&”,用于错误地HTML编码的URL)
)#结束非捕获组
(#组1
  [^ =&#] +#除“ =“,“&”或“#”外的任何字符;至少一次
)#结束组1-这将是参数的名称
(?:#非捕获组
  =?#一个“ =”,可选
  (#组2
    [^&#] *#除“&”或“#”外的任何字符;任何次数
  )#结束组2-这将是参数的值
)#结束非捕获组

23
这就是我所希望的。我从未在JavaScript文档中看到过提及exec()方法,如果多次调用将继续返回下一个结果集。再次感谢您的提示!
亚当·佛朗哥

1
这样做的原因是:regular-expressions.info/javascript.html(通读:“如何使用JavaScript RegExp对象”)
Tomalak

1
这段代码中有一个错误:“ while”后的分号应删除。
Jan Willem B 2010年

1
因为我通常只对正常的内容感兴趣,所以才使用普通的(即捕获)群组。
Tomalak 2013年

1
@KnightYoshi是的。在JavaScript中,任何表达式也会产生自己的结果(就像x = y将赋值yx并且也产生一样y)。当我们将该知识应用于if (match = re.exec(url)):这是A)进行的赋值, B)将的结果返回re.exec(url)while。现在,如果没有匹配,则re.exec返回null假值。因此,实际上只要存在匹配项,循环就会继续。
Tomalak 2014年

67

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

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

33
这实际上并不能解决问题:“使用全局标志'g'将匹配所有匹配项,但仅返回完全匹配的子字符串,而不返回分离的键和值。”
亚当·佛朗哥

40

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(/(?:&|&)?([^=]+)=([^&]+)/g, getKeyValue);

完成。

与其使用捕获组处理功能实际返回替换字符串(对于替换处理,第一个arg是完全模式匹配,而随后的args是单独的捕获组),我们只需要捕获第2组和第3组捕获,并缓存该对。

因此,请记住,JavaScript中的“ matchAll”函数只是用替换处理程序函数“替换”,而不是编写复杂的解析函数,并且可以获得很多模式匹配效率。


我有绳子something "this one" and "that one"。我想将所有用双引号括起来的字符串放在一个列表中,例如[this,that one]。到目前为止,mystring.match(/"(.*?)"/)可以很好地检测第一个捕获组,但是我不知道如何针对单个捕获组调整您的解决方案。
nu Everest,2014年

2
听起来您应该为此在Stackoverflow上发布问题,而不是尝试在评论中解决问题。
Mike'Pomax'Kamermans 2014年

:我创建了一个新的问题stackoverflow.com/questions/26174122/...
NU珠峰

1
不知道为什么这个答案投票很少,但这是对这个问题的最佳答案。
卡林2015年

嗨@ Mike'Pomax'Kamermans,社区指南特别建议编辑条目以改进它们,请参阅:stackoverflow.com/help/behavior。您的答案的核心非常有用,但是我发现“记住matchAll被替换”的语言尚不清楚,也无法解释您的代码(不明显)为什么起作用。我认为您应该得到当之无愧的代表,所以我编辑了您的答案,而不是用改进的文本来复制它。作为此问题的原始提问者,如果您仍然希望我答复,我很高兴恢复对此答案(和编辑内容)的接受。
亚当·佛朗哥

21

对于捕获组,我习惯于preg_match_all在PHP中使用,并且尝试在此处复制其功能:

<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>

3
@teh_senaus您需要指定global修饰符,/g否则运行exec()将不会更改当前索引,并且将永远循环。
Aram Kocharyan 2014年

如果我致电验证此代码myRe.test(str),然后尝试执行execAll,它将在第二场比赛开始,而我们输掉了第一场比赛。
fdrv

@fdrv您必须在开始循环之前将lastIndex重置为零:this.lastIndex = 0;
CF CF

15

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

/…/g

11
这实际上并不能解决问题:“使用全局标志'g'将匹配所有匹配项,但仅返回完全匹配的子字符串,而不返回分离的键和值。”
亚当·佛朗哥

11

来源: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属性而导致匹配时,它将创建无限循环。还要确保设置了全局标志,否则也会在此处发生循环。


如果我致电验证此代码myRe.test(str),然后尝试做一会儿,它将在第二场比赛开始,而我们输掉了第一场比赛。
fdrv

您还可以将String.prototype.match其与g标志结合使用: 'abbcdefabh'.match(/ab*/g)返回['abb', 'ab']
thom_nic

2

如果某人(如我)需要具有数组支持的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(仅先前返回:事物)


1

只是坚持标题所建议的问题,实际上您可以使用来遍历字符串中的每个匹配项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;
}

由于您想解析URL的查询字符串,因此您也可以使用YourJS.parseQS()yourjs.com/snippets/56)之类的东西,尽管许多其他库也提供了此功能。
克里斯·韦斯特

在应该返回替换的循环中从外部作用域修改变量有点不好。您此处的误用替换
Juan Mendes

1

如果您可以避免使用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);

不够漂亮,效率不高,但至少它是紧凑的。;)


1

用途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"]]

1

从2020年开始。请让我注意String.prototype.matchAll()

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

最后!注意事项:“ ECMAScript 2020,第11版,为String引入了matchAll方法,以为由全局正则表达式生成的所有匹配对象生成一个迭代器”。根据答案中链接的网站,大多数浏览器和nodeJS当前都支持它,但IE,Safari或Samsung Internet不支持。希望支持会很快扩大,但是YMMV会持续一段时间。
亚当·佛朗哥

0

为了使用相同的名称捕获多个参数,我在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"]}


尽管我喜欢您的想法,但对于单个参数来说效果并不理想,就像?cinema=1234&film=12&film=34我期望的那样{cinema: 1234, film: [12, 34]}。编辑您的答案以反映这一点。
TWiStErRob

0

好吧...我有一个类似的问题...我想用RegExp进行增量/逐步搜索(例如:开始搜索...进行一些处理...继续搜索直到最后一次匹配)

经过大量的互联网搜索之后……像往常一样(现在正在养成习惯),我最终在StackOverflow中找到了答案……

什么没有被提及,值得提及的是“ lastIndex”我现在理解为什么RegExp对象实现“ lastIndex”属性。


0

拆分它似乎是我的最佳选择:

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

0

为了避免正则表达式地狱,您可以找到第一个匹配项,将其切碎,然后尝试在子字符串中找到下一个匹配项。在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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.