【问题标题】:How can I match multiple occurrences with a regex in JavaScript similar to PHP's preg_match_all()?如何使用类似于 PHP 的 preg_match_all() 的 JavaScript 中的正则表达式匹配多次出现?
【发布时间】:2010-10-05 22:58:44
【问题描述】:

我正在尝试解析由 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']]

【问题讨论】:

  • 这里没有人推荐使用replace 有点奇怪。 var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; }); 完成。 JavaScript 中的“matchAll”是用替换处理函数而不是字符串“替换”。
  • 请注意,对于那些在 2020 年仍然发现这个问题的人,答案是“不要使用正则表达式,使用 URLSearchParams,它会为您完成所有这些。”

标签: javascript regex


【解决方案1】:

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

/…/g

【讨论】:

  • 这实际上并不能解决问题:“使用全局标志 'g' 将匹配所有匹配项,但只返回完全匹配的子字符串,而不是分离的键和值。”
【解决方案2】:

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

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

【讨论】:

  • 这实际上并不能解决问题:“使用全局标志 'g' 将匹配所有匹配项,但只返回完全匹配的子字符串,而不是分离的键和值。”
【解决方案3】:

从 cmets 吊起

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

——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:“德” 即:“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 - 这将是参数的名称 (?: # 非捕获组 =? # 一个“=”,可选 (#第二组 [^]* # 除“&”或“#”外的任何字符;任意次数 ) # 结束组 2 - 这将是参数的值 ) # 结束非捕获组

【讨论】:

  • 这是我所希望的。我从未在 JavaScript 文档中看到过,如果多次调用 exec() 方法,它将继续返回下一个结果集。再次感谢您的精彩提示!
  • 之所以这样做是因为:regular-expressions.info/javascript.html(通读:“如何使用 JavaScript RegExp 对象”)
  • 这段代码有个bug:while后面的分号要去掉。
  • 因为如果我真的对他们的内容感兴趣,我通常只使用普通(即捕获)组。
  • @KnightYoshi 是的。在 JavaScript 中,任何表达式也会产生它自己的结果(比如 x = y 会将 y 分配给 x 并且还会产生 y)。当我们将该知识应用于if (match = re.exec(url)) 时: A) 执行赋值并且 B) 将re.exec(url) 的结果返回给while。现在re.exec 如果不匹配,则返回null,这是一个假值。所以实际上只要有匹配项,循环就会继续。
【解决方案4】:

对于捕获组,我习惯于在 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>

【讨论】:

  • @teh_senaus 您需要使用/g 指定全局修饰符,否则运行exec() 不会更改当前索引并且将永远循环。
  • 如果我调用验证此代码 myRe.test(str),然后尝试执行 execAll,它会在第二场比赛中出现,我们输掉了第一场比赛。
  • @fdrv 您必须在开始循环之前将 lastIndex 重置为零:this.lastIndex = 0;
【解决方案5】:

嗯...我遇到了类似的问题... 我想要使​​用 RegExp 进行增量/步进搜索 (例如:开始搜索...做一些处理...继续搜索直到最后一个匹配)

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

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

【讨论】:

    【解决方案6】:

    为了捕获使用相同名称的多个参数,我修改了 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&amp;lastname=bush&amp;firstname=bill&amp;lastname=clinton

    返回:{firstname : ["george", "bill"], lastname : ["bush", "clinton"]}

    【讨论】:

    • 虽然我喜欢你的想法,但它不适用于单个参数,例如 ?cinema=1234&amp;film=12&amp;film=34 我希望 {cinema: 1234, film: [12, 34]}。编辑您的答案以反映这一点。
    【解决方案7】:

    如果有人(比如我)需要支持数组的 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&amp;my=2&amp;my=things

    结果1,2,things(之前只返回:东西)

    【讨论】:

      【解决方案8】:

      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 确实内置了 match-all 功能,但它是 replace 函数调用的一部分,当使用“如何处理捕获组”handling function

      var data = {};
      
      var getKeyValue = function(fullPattern, group1, group2, group3) {
        data[group2] = group3;
      };
      
      mystring.replace(/(?:&|&amp;)?([^=]+)=([^&]+)/g, getKeyValue);
      

      完成。

      我们没有使用捕获组处理函数来实际返回替换字符串(对于替换处理,第一个 arg 是完整的模式匹配,随后的 args 是单独的捕获组)我们只是获取组 2 和 3 捕获,然后缓存那对。

      因此,不要编写复杂的解析函数,记住 JavaScript 中的“matchAll”函数只是简单地用替换处理函数“替换”,可以提高模式匹配效率。

      【讨论】:

      • 我有一个字符串something "this one" and "that one"。我想将所有双引号字符串放在一个列表中,即[这个,那个]。到目前为止,mystring.match(/"(.*?)"/) 可以很好地检测到第一个,但我不知道如何为单个捕获组调整您的解决方案。
      • 听起来你应该为此在 Stackoverflow 上发布一个问题,而不是尝试在 cmets 中解决它。
      • 我创建了一个新问题:stackoverflow.com/questions/26174122/…
      • 不知道为什么这个答案有这么少的赞成票,但它是问题的最佳答案。
      • 嗨@Mike'Pomax'Kamermans,社区指南特别建议编辑条目以改进它们,请参阅:stackoverflow.com/help/behavior。您的回答的核心非常有帮助,但我发现“记住 matchAll 是替换”的语言并不清楚,也不能解释为什么您的代码(不明显)有效。我认为你应该得到当之无愧的代表,所以我编辑了你的答案,而不是用改进的文本复制它。作为这个问题的原始提问者,如果您仍然希望我接受,我很高兴恢复对这个答案(和编辑)的接受。
      【解决方案9】:

      为了坚持标题所示的建议问题,您实际上可以使用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) 的东西,尽管很多其他的库也提供了这个功能。
      • 在应该返回替换的循环中从外部范围修改变量有点糟糕。你的误用替换在这里
      【解决方案10】:

      来源:
      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) 然后尝试 do while,它会在第二场比赛中出现,我们输掉了第一场比赛。
      • 您还可以将String.prototype.matchg 标志结合使用:'abbcdefabh'.match(/ab*/g) 返回['abb', 'ab']
      【解决方案11】:

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

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

      【讨论】:

        【解决方案12】:

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

        【讨论】:

          【解决方案13】:

          在我看来,拆分它是最好的选择:

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

          【讨论】:

            【解决方案14】:

            为了避免正则表达式地狱,你可以找到你的第一个匹配,切掉一个块然后尝试在子字符串上找到下一个。在 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;
            

            【讨论】:

              【解决方案15】:

              您好,从 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
              

              【讨论】:

              猜你喜欢
              • 2012-07-05
              • 2014-08-01
              • 2012-02-24
              • 1970-01-01
              • 2015-09-19
              • 1970-01-01
              • 1970-01-01
              • 2015-07-24
              • 1970-01-01
              相关资源
              最近更新 更多