是否有允许正则表达式的JavaScript String.indexOf()版本?

时间:2008-11-07 22:03:34

标签: javascript regex indexof

在javascript中,是否有一个等效的String.indexOf()为第一个第一个参数采用正则表达式而不是字符串,同时仍然允许第二个参数?

我需要做类似

的事情
str.indexOf(/[abc]/ , i);

str.lastIndexOf(/[abc]/ , i);

虽然String.search()将regexp作为参数,但它不允许我指定第二个参数!

编辑:
事实证明这比我原先想象的要难,所以我编写了一个小测试函数来测试所有提供的解决方案......它假设regexIndexOf和regexLastIndexOf已被添加到String对象中。

function test (str) {
    var i = str.length +2;
    while (i--) {
        if (str.indexOf('a',i) != str.regexIndexOf(/a/,i)) 
            alert (['failed regexIndexOf ' , str,i , str.indexOf('a',i) , str.regexIndexOf(/a/,i)]) ;
        if (str.lastIndexOf('a',i) != str.regexLastIndexOf(/a/,i) ) 
            alert (['failed regexLastIndexOf ' , str,i,str.lastIndexOf('a',i) , str.regexLastIndexOf(/a/,i)]) ;
    }
}

我正在测试如下以确保至少对于一个字符regexp,结果与我们使用indexOf

相同

//在xes中查找a 试验( 'XXX');
试验( 'AXX');
试验( 'XAX');
试验( 'XXA');
试验( 'AXA');
试验( '的Xaa');
试验( 'AAX');
试验( 'AAA');

18 个答案:

答案 0 :(得分:163)

String构造函数的实例有一个.search() method,它接受​​一个RegExp并返回第一个匹配的索引。

要从特定位置开始搜索(伪造.indexOf()的第二个参数),您可以slice关闭第一个i个字符:

str.slice(i).search(/re/)

但是这将使得索引在较短的字符串中(在第一部分被切掉之后),所以你想要将截断的部分(i)的长度添加到返回的索引中,如果它不是-1。这将为您提供原始字符串中的索引:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}

答案 1 :(得分:122)

结合已经提到的一些方法(indexOf显然相当简单),我认为这些功能可以解决这个问题:

String.prototype.regexIndexOf = function(regex, startpos) {
    var indexOf = this.substring(startpos || 0).search(regex);
    return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
    regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
    if(typeof (startpos) == "undefined") {
        startpos = this.length;
    } else if(startpos < 0) {
        startpos = 0;
    }
    var stringToWorkWith = this.substring(0, startpos + 1);
    var lastIndexOf = -1;
    var nextStop = 0;
    while((result = regex.exec(stringToWorkWith)) != null) {
        lastIndexOf = result.index;
        regex.lastIndex = ++nextStop;
    }
    return lastIndexOf;
}

显然,修改内置String对象会为大多数人发送红色标记,但这可能是一次没有那么大的交易;只是意识到它。


更新:已编辑regexLastIndexOf(),因此现在似乎模仿了lastIndexOf()。如果它仍然失败并且在什么情况下,请告诉我。


更新:通过本页评论中找到的所有测试,以及我自己的测试。当然,这并不意味着它是防弹的。任何反馈意见。

答案 2 :(得分:33)

我有一个简短的版本。它适用于我!

var match      = str.match(/[abc]/gi);
var firstIndex = str.indexOf(match[0]);
var lastIndex  = str.lastIndexOf(match[match.length-1]);

如果你想要一个原型版本:

String.prototype.indexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.indexOf(match[0]) : -1;
}

String.prototype.lastIndexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.lastIndexOf(match[match.length-1]) : -1;
}

编辑:如果您想添加对fromIndex的支持

String.prototype.indexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(fromIndex) : this;
  var match = str.match(regex);
  return match ? str.indexOf(match[0]) + fromIndex : -1;
}

String.prototype.lastIndexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(0, fromIndex) : this;
  var match = str.match(regex);
  return match ? str.lastIndexOf(match[match.length-1]) : -1;
}

要使用它,就像这样简单:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);

答案 3 :(得分:13)

使用:

str.search(regex)

请参阅文档here.

答案 4 :(得分:6)

基于BaileyP的回答。主要区别在于,如果模式无法匹配,这些方法将返回-1

编辑:感谢Jason Bunting的回答,我有了一个主意。为什么不修改正则表达式的.lastIndex属性?虽然这只适用于带有全局标志(/g)的模式。

修改:已更新以通过测试用例。

String.prototype.regexIndexOf = function(re, startPos) {
    startPos = startPos || 0;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    re.lastIndex = startPos;
    var match = re.exec(this);

    if (match) return match.index;
    else return -1;
}

String.prototype.regexLastIndexOf = function(re, startPos) {
    startPos = startPos === undefined ? this.length : startPos;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    var lastSuccess = -1;
    for (var pos = 0; pos <= startPos; pos++) {
        re.lastIndex = pos;

        var match = re.exec(this);
        if (!match) break;

        pos = match.index;
        if (pos <= startPos) lastSuccess = pos;
    }

    return lastSuccess;
}

答案 5 :(得分:6)

您可以使用substr。

str.substr(i).match(/[abc]/);

答案 6 :(得分:4)

它本身并不存在,但您当然可以添加此功能

<script type="text/javascript">

String.prototype.regexIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex || 0;
    var searchResult = this.substr( startIndex ).search( pattern );
    return ( -1 === searchResult ) ? -1 : searchResult + startIndex;
}

String.prototype.regexLastIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex === undefined ? this.length : startIndex;
    var searchResult = this.substr( 0, startIndex ).reverse().regexIndexOf( pattern, 0 );
    return ( -1 === searchResult ) ? -1 : this.length - ++searchResult;
}

String.prototype.reverse = function()
{
    return this.split('').reverse().join('');
}

// Indexes 0123456789
var str = 'caabbccdda';

alert( [
        str.regexIndexOf( /[cd]/, 4 )
    ,   str.regexLastIndexOf( /[cd]/, 4 )
    ,   str.regexIndexOf( /[yz]/, 4 )
    ,   str.regexLastIndexOf( /[yz]/, 4 )
    ,   str.lastIndexOf( 'd', 4 )
    ,   str.regexLastIndexOf( /d/, 4 )
    ,   str.lastIndexOf( 'd' )
    ,   str.regexLastIndexOf( /d/ )
    ]
);

</script>

我没有完全测试这些方法,但它们似乎到目前为止都有效。

答案 7 :(得分:4)

RexExp个实例已经具有lastIndex属性(如果它们是全局的),所以我正在做的是复制正则表达式,稍微修改它以适合我们的目的,exec - 在字符串上查看lastIndex。这将不可避免地比在字符串上循环更快。 (你有足够的例子说明如何将它放到字符串原型上,对吗?)

function reIndexOf(reIn, str, startIndex) {
    var re = new RegExp(reIn.source, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

function reLastIndexOf(reIn, str, startIndex) {
    var src = /\$$/.test(reIn.source) && !/\\\$$/.test(reIn.source) ? reIn.source : reIn.source + '(?![\\S\\s]*' + reIn.source + ')';
    var re = new RegExp(src, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

reIndexOf(/[abc]/, "tommy can eat");  // Returns 6
reIndexOf(/[abc]/, "tommy can eat", 8);  // Returns 11
reLastIndexOf(/[abc]/, "tommy can eat"); // Returns 11

您还可以将函数原型化到RegExp对象上:

RegExp.prototype.indexOf = function(str, startIndex) {
    var re = new RegExp(this.source, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

RegExp.prototype.lastIndexOf = function(str, startIndex) {
    var src = /\$$/.test(this.source) && !/\\\$$/.test(this.source) ? this.source : this.source + '(?![\\S\\s]*' + this.source + ')';
    var re = new RegExp(src, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};


/[abc]/.indexOf("tommy can eat");  // Returns 6
/[abc]/.indexOf("tommy can eat", 8);  // Returns 11
/[abc]/.lastIndexOf("tommy can eat"); // Returns 11

快速解释我如何修改RegExp:对于indexOf我只需要确保设置全局标志。对于lastIndexOf,我使用否定前瞻来查找最后一次出现,除非RegExp已经在字符串的末尾匹配。

答案 8 :(得分:2)

让所有提议的解决方案以某种方式失败我的测试后,(编辑:在我写完之后更新了一些以通过测试)我找到了Array.indexOfArray.lastIndexOf的mozilla实现

我使用它们来实现我的String.prototype.regexIndexOf和String.prototype.regexLastIndexOf版本,如下所示:

String.prototype.regexIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++) {
      if (from in arr && elt.exec(arr[from]) ) 
        return from;
    }
    return -1;
};

String.prototype.regexLastIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]);
    if (isNaN(from)) {
      from = len - 1;
    } else {
      from = (from < 0) ? Math.ceil(from) : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len - 1;
    }

    for (; from > -1; from--) {
      if (from in arr && elt.exec(arr[from]) )
        return from;
    }
    return -1;
  };

他们似乎通过了我在问题中提供的测试功能。

显然,只有当正则表达式匹配一个字符时它们才有效,但这对我的目的来说已经足够了,因为我将它用于诸如([abc],\ s,\ W,\ D)之类的东西

我会继续监控这个问题,以防有人提供更好/更快/更清洁/更通用的实现,适用于任何正则表达式。

答案 9 :(得分:2)

我还需要一个regexIndexOf函数用于数组,所以我自己编写了一个函数。但我怀疑,它已经过优化,但我想它应该可以正常工作。

Array.prototype.regexIndexOf = function (regex, startpos = 0) {
    len = this.length;
    for(x = startpos; x < len; x++){
        if(typeof this[x] != 'undefined' && (''+this[x]).match(regex)){
            return x;
        }
    }
    return -1;
}

arr = [];
arr.push(null);
arr.push(NaN);
arr[3] = 7;
arr.push('asdf');
arr.push('qwer');
arr.push(9);
arr.push('...');
console.log(arr);
arr.regexIndexOf(/\d/, 4);

答案 10 :(得分:1)

在某些简单的情况下,您可以使用拆分来简化向后搜索。

function regexlast(string,re){
  var tokens=string.split(re);
  return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}

这有一些严重的问题:

  1. 重叠匹配不会显示
  2. 返回的索引是匹配结束而不是开始(如果你的正则表达式是一个常数,则很好)
  3. 但从好的方面来看,代码更少。对于不能重叠的恒定长度正则表达式(如/\s\w/来查找单词边界),这已经足够了。

答案 11 :(得分:0)

对于具有稀疏匹配的数据,使用string.search是浏览器中最快的。它会在每次迭代时将字符串重新切片为:

function lastIndexOfSearch(string, regex, index) {
  if(index === 0 || index)
     string = string.slice(0, Math.max(0,index));
  var idx;
  var offset = -1;
  while ((idx = string.search(regex)) !== -1) {
    offset += idx + 1;
    string = string.slice(idx + 1);
  }
  return offset;
}

对于密集数据,我做了这个。与执行方法相比,它复杂,但对于密集数据,它比我尝试的其他方法快2-10倍,比接受的解决方案快约100倍。要点是:

  1. 它会在传入的正则表达式上调用exec以验证是否匹配或提前退出。我这样做是使用(?=在类似的方法中,但在使用exec进行IE检查时要快得多。
  2. 它以&#39;(r)格式构建和缓存修改后的正则表达式。(?!。?r)&#39;
  3. 执行新的正则表达式并返回exec或第一个exec的结果;

    function lastIndexOfGroupSimple(string, regex, index) {
        if (index === 0 || index) string = string.slice(0, Math.max(0, index + 1));
        regex.lastIndex = 0;
        var lastRegex, index
        flags = 'g' + (regex.multiline ? 'm' : '') + (regex.ignoreCase ? 'i' : ''),
        key = regex.source + '$' + flags,
        match = regex.exec(string);
        if (!match) return -1;
        if (lastIndexOfGroupSimple.cache === undefined) lastIndexOfGroupSimple.cache = {};
        lastRegex = lastIndexOfGroupSimple.cache[key];
        if (!lastRegex)
            lastIndexOfGroupSimple.cache[key] = lastRegex = new RegExp('.*(' + regex.source + ')(?!.*?' + regex.source + ')', flags);
        index = match.index;
        lastRegex.lastIndex = match.index;
        return (match = lastRegex.exec(string)) ? lastRegex.lastIndex - match[1].length : index;
    };
    
  4. jsPerf of methods

    我不了解测试的目的。需要正则表达式的情况无法与对indexOf的调用进行比较,我认为这是首先制定该方法的重点。为了让测试通过,使用&#39; xxx +(?!x)&#39;更有意义,而不是调整正则表达式迭代的方式。

答案 12 :(得分:0)

杰森·邦廷的最后一个指数不起作用。我的不是最优的,但它确实有效。

//Jason Bunting's
String.prototype.regexIndexOf = function(regex, startpos) {
var indexOf = this.substring(startpos || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
var lastIndex = -1;
var index = this.regexIndexOf( regex );
startpos = startpos === undefined ? this.length : startpos;

while ( index >= 0 && index < startpos )
{
    lastIndex = index;
    index = this.regexIndexOf( regex, index + 1 );
}
return lastIndex;
}

答案 13 :(得分:0)

如果您要使用RegExp查找非常简单的lastIndex查找,并且不在乎它是否将lastIndexOf模仿到最后一个细节,那么这可能会引起您的注意。

我只是反转字符串,并从长度-1中减去第一个出现索引。它碰巧通过了我的测试,但是我认为长字符串可能会出现性能问题。

interface String {
  reverse(): string;
  lastIndex(regex: RegExp): number;
}

String.prototype.reverse = function(this: string) {
  return this.split("")
    .reverse()
    .join("");
};

String.prototype.lastIndex = function(this: string, regex: RegExp) {
  const exec = regex.exec(this.reverse());
  return exec === null ? -1 : this.length - 1 - exec.index;
};

答案 14 :(得分:0)

我使用了String.prototype.match(regex),它返回字符串中给定regex的所有找到的匹配项的字符串数组(更多信息see here):

function getLastIndex(text, regex, limit = text.length) {
  const matches = text.match(regex);

  // no matches found
  if (!matches) {
    return -1;
  }

  // matches found but first index greater than limit
  if (text.indexOf(matches[0] + matches[0].length) > limit) {
    return -1;
  }

  // reduce index until smaller than limit
  let i = matches.length - 1;
  let index = text.lastIndexOf(matches[i]);
  while (index > limit && i >= 0) {
    i--;
    index = text.lastIndexOf(matches[i]);
  }
  return index > limit ? -1 : index;
}

// expect -1 as first index === 14
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g, 10));

// expect 29
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g));

答案 15 :(得分:0)

var mystring = "abc ab a";
var re  = new RegExp("ab"); // any regex here

if ( re.exec(mystring) != null ){ 
   alert("matches"); // true in this case
}

使用标准正则表达式:

var re  = new RegExp("^ab");  // At front
var re  = new RegExp("ab$");  // At end
var re  = new RegExp("ab(c|d)");  // abc or abd

答案 16 :(得分:0)

对于比发布的大多数其他答案更简洁的解决方案,您可能需要使用 String.prototype.replace 函数,该函数将对每个检测到的模式运行一个函数。例如:

let firstIndex = -1;
"the 1st numb3r".replace(/\d/,(p,i) => { firstIndex = i; });
// firstIndex === 4

这对于“最后一个索引”的情况特别有用:

let lastIndex = -1;
"the l4st numb3r".replace(/\d/g,(p,i) => { lastIndex = i; });
// lastIndex === 13

在这里,重要的是包含“g”修饰符,以便评估所有出现的情况。如果找不到正则表达式,这些版本也会导致 -1

最后,这里是包含起始索引的更通用的函数:

function indexOfRegex(str,regex,start = 0) {
    regex = regex.global ? regex : new RegExp(regex.source,regex.flags + "g");
    let index = -1;
    str.replace(regex,function() {
        const pos = arguments[arguments.length - 2];
        if(index < 0 && pos >= start)
            index = pos;
    });
    return index;
}

function lastIndexOfRegex(str,regex,start = str.length - 1) {
    regex = regex.global ? regex : new RegExp(regex.source,regex.flags + "g");
    let index = -1;
    str.replace(regex,function() {
        const pos = arguments[arguments.length - 2];
        if(pos <= start)
            index = pos;
    });
    return index;
}

这些函数专门避免在开始索引处拆分字符串,我认为这在 Unicode 时代是有风险的。它们不会修改常见 Javascript 类的原型(尽管您可以自行修改)。它们接受更多的 RegExp 标志,例如“u”或“s”以及将来可能添加的任何标志。而且我发现回调函数比 for/while 循环更容易推理。

答案 17 :(得分:-2)

好吧,因为你只是想匹配字符的位置,正则表达式可能有点过分。

我认为你想要的只是找到这些角色中的第一个,而不是“先找到这个角色”。

这当然是一个简单的答案,但你的问题确实要做什么,虽然没有正则表达式部分(因为你没有澄清为什么特别是它必须是一个正则表达式)

function mIndexOf( str , chars, offset )
{
   var first  = -1; 
   for( var i = 0; i < chars.length;  i++ )
   {
      var p = str.indexOf( chars[i] , offset ); 
      if( p < first || first === -1 )
      {
           first = p;
      }
   }
   return first; 
}
String.prototype.mIndexOf = function( chars, offset )
{
   return mIndexOf( this, chars, offset ); # I'm really averse to monkey patching.  
};
mIndexOf( "hello world", ['a','o','w'], 0 );
>> 4 
mIndexOf( "hello world", ['a'], 0 );
>> -1 
mIndexOf( "hello world", ['a','o','w'], 4 );
>> 4
mIndexOf( "hello world", ['a','o','w'], 5 );
>> 6
mIndexOf( "hello world", ['a','o','w'], 7 );
>> -1 
mIndexOf( "hello world", ['a','o','w','d'], 7 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 10 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 11 );
>> -1