是否有一个javascript的String.indexOf()版本允许使用正则表达式?

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

我需要做点什么

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

而且

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

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

< p >编辑:< br > 这比我最初想象的要难,所以我写了一个小测试函数来测试所有提供的解决方案……它假设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,结果是相同的

//在x中查找a
测试(“xxx”);< br > 测试(axx); < br > 测试(xax); < br > 测试(xxa); < br > 测试(axa); < br > 测试(xaa); < br > 测试(aax); < br > 测试(' aaa ');< / p >

209074 次浏览

你可以使用substr。

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

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

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

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

但这将获得较短字符串的索引(在第一部分被切掉之后),因此如果返回的索引不是-1,则需要将被切掉部分的长度(i)添加到返回的索引中。这将给你原始字符串的索引:

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

它不是原生的,但您当然可以添加此功能

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

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

根据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;
}

好吧,因为你只是想匹配字符的位置,regex可能是多余的。

我假设你想要的不是,找到这些字符中的第一个,而是找到这些字符中的第一个。

这当然是一个简单的答案,但做到了你的问题所要做的事情,尽管没有正则表达式部分(因为你没有明确说明为什么它必须是一个正则表达式)

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

结合已经提到的一些方法(indexOf显然相当简单),我认为这些函数将达到目的:

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


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

更新:编辑了regexLastIndexOf(),现在看起来是模仿lastIndexOf()。请让我知道它是否仍然失败,在什么情况下。


更新:通过本页评论和我自己的所有测试。当然,这并不意味着它是防弹的。感谢任何反馈。

在所有提出的解决方案都以某种方式失败我的测试之后(编辑:一些在我写这篇文章后更新为通过测试),我找到了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)之类的事情。

我将继续关注这个问题,以防有人提供更好/更快/更干净/更通用的实现,适用于任何正则表达式。

RexExp实例已经有一个lastIndex属性(如果它们是全局的),所以我所做的是复制正则表达式,稍微修改它以适合我们的目的,在字符串上exec-ing它,并查看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已经在字符串的末尾匹配,否则我将使用负向前查找最后一次出现。

我还需要一个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);

在某些简单的情况下,您可以通过使用split简化向后搜索。

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

这有一些严重的问题:

  1. 重叠的匹配不会显示出来
  2. 返回的索引是匹配的结束,而不是开始(如果你的regex是一个常量,没问题)

但从好的方面来看,它的代码更少。对于不能重叠的定长正则表达式(如/\s\w/用于查找单词边界),这已经足够好了。

我有一个简短的版本给你。这对我来说很有效!

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

对于具有稀疏匹配的数据,使用字符串。跨浏览器搜索速度最快。它每次迭代都会重新切片字符串:

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,以验证是否匹配或提前退出。我使用(?=类似的方法,但在IE上用exec检查要快得多。
  2. 它构造并缓存格式为'(r).(? !。?r)'的修改正则表达式
  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;
    };
    

jsPerf of methods

I don't understand the purpose of the tests up top. Situations that require a regex are impossible to compare against a call to indexOf, which I think is the point of making the method in the first place. To get the test to pass, it makes more sense to use 'xxx+(?!x)', than adjust the way the regex iterates.

杰森·邦廷的最后一个指数不成立。我的方法不是最优的,但有效。

//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;
}

使用:

str.search(regex)

参见文档在这里。

仍然没有执行请求任务的本机方法。

这是我正在使用的代码。它模仿String.prototype.indexOfString.prototype.lastIndexOf方法的行为,但除了表示要搜索的值的字符串外,它们还接受RegExp作为搜索参数。

是的,它是相当长的答案,因为它试图遵循当前的标准尽可能接近,当然包含了合理数量的JSDOC注释。然而,一旦缩小,代码只有2.27k,一旦gzip传输,它只有1023字节。

添加到String.prototype的两个方法(在可用的情况下使用Object.defineProperty)是:

  1. searchOf
  2. searchLastOf

它通过了OP发布的所有测试,此外,我在日常使用中已经相当彻底地测试了这些例程,并试图确保它们在多个环境中工作,但反馈/问题总是受欢迎的。

.
/*jslint maxlen:80, browser:true */


/*
* Properties used by searchOf and searchLastOf implementation.
*/


/*property
MAX_SAFE_INTEGER, abs, add, apply, call, configurable, defineProperty,
enumerable, exec, floor, global, hasOwnProperty, ignoreCase, index,
lastIndex, lastIndexOf, length, max, min, multiline, pow, prototype,
remove, replace, searchLastOf, searchOf, source, toString, value, writable
*/


/*
* Properties used in the testing of searchOf and searchLastOf implimentation.
*/


/*property
appendChild, createTextNode, getElementById, indexOf, lastIndexOf, length,
searchLastOf, searchOf, unshift
*/


(function () {
'use strict';


var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
getNativeFlags = new RegExp('\\/([a-z]*)$', 'i'),
clipDups = new RegExp('([\\s\\S])(?=[\\s\\S]*\\1)', 'g'),
pToString = Object.prototype.toString,
pHasOwn = Object.prototype.hasOwnProperty,
stringTagRegExp;


/**
* Defines a new property directly on an object, or modifies an existing
* property on an object, and returns the object.
*
* @private
* @function
* @param {Object} object
* @param {string} property
* @param {Object} descriptor
* @returns {Object}
* @see https://goo.gl/CZnEqg
*/
function $defineProperty(object, property, descriptor) {
if (Object.defineProperty) {
Object.defineProperty(object, property, descriptor);
} else {
object[property] = descriptor.value;
}


return object;
}


/**
* Returns true if the operands are strictly equal with no type conversion.
*
* @private
* @function
* @param {*} a
* @param {*} b
* @returns {boolean}
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
*/
function $strictEqual(a, b) {
return a === b;
}


/**
* Returns true if the operand inputArg is undefined.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
*/
function $isUndefined(inputArg) {
return $strictEqual(typeof inputArg, 'undefined');
}


/**
* Provides a string representation of the supplied object in the form
* "[object type]", where type is the object type.
*
* @private
* @function
* @param {*} inputArg The object for which a class string represntation
*                     is required.
* @returns {string} A string value of the form "[object type]".
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
*/
function $toStringTag(inputArg) {
var val;
if (inputArg === null) {
val = '[object Null]';
} else if ($isUndefined(inputArg)) {
val = '[object Undefined]';
} else {
val = pToString.call(inputArg);
}


return val;
}


/**
* The string tag representation of a RegExp object.
*
* @private
* @type {string}
*/
stringTagRegExp = $toStringTag(getNativeFlags);


/**
* Returns true if the operand inputArg is a RegExp.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
*/
function $isRegExp(inputArg) {
return $toStringTag(inputArg) === stringTagRegExp &&
pHasOwn.call(inputArg, 'ignoreCase') &&
typeof inputArg.ignoreCase === 'boolean' &&
pHasOwn.call(inputArg, 'global') &&
typeof inputArg.global === 'boolean' &&
pHasOwn.call(inputArg, 'multiline') &&
typeof inputArg.multiline === 'boolean' &&
pHasOwn.call(inputArg, 'source') &&
typeof inputArg.source === 'string';
}


/**
* The abstract operation throws an error if its argument is a value that
* cannot be converted to an Object, otherwise returns the argument.
*
* @private
* @function
* @param {*} inputArg The object to be tested.
* @throws {TypeError} If inputArg is null or undefined.
* @returns {*} The inputArg if coercible.
* @see https://goo.gl/5GcmVq
*/
function $requireObjectCoercible(inputArg) {
var errStr;


if (inputArg === null || $isUndefined(inputArg)) {
errStr = 'Cannot convert argument to object: ' + inputArg;
throw new TypeError(errStr);
}


return inputArg;
}


/**
* The abstract operation converts its argument to a value of type string
*
* @private
* @function
* @param {*} inputArg
* @returns {string}
* @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
*/
function $toString(inputArg) {
var type,
val;


if (inputArg === null) {
val = 'null';
} else {
type = typeof inputArg;
if (type === 'string') {
val = inputArg;
} else if (type === 'undefined') {
val = type;
} else {
if (type === 'symbol') {
throw new TypeError('Cannot convert symbol to string');
}


val = String(inputArg);
}
}


return val;
}


/**
* Returns a string only if the arguments is coercible otherwise throws an
* error.
*
* @private
* @function
* @param {*} inputArg
* @throws {TypeError} If inputArg is null or undefined.
* @returns {string}
*/
function $onlyCoercibleToString(inputArg) {
return $toString($requireObjectCoercible(inputArg));
}


/**
* The function evaluates the passed value and converts it to an integer.
*
* @private
* @function
* @param {*} inputArg The object to be converted to an integer.
* @returns {number} If the target value is NaN, null or undefined, 0 is
*                   returned. If the target value is false, 0 is returned
*                   and if true, 1 is returned.
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
*/
function $toInteger(inputArg) {
var number = +inputArg,
val = 0;


if ($strictEqual(number, number)) {
if (!number || number === Infinity || number === -Infinity) {
val = number;
} else {
val = (number > 0 || -1) * Math.floor(Math.abs(number));
}
}


return val;
}


/**
* Copies a regex object. Allows adding and removing native flags while
* copying the regex.
*
* @private
* @function
* @param {RegExp} regex Regex to copy.
* @param {Object} [options] Allows specifying native flags to add or
*                           remove while copying the regex.
* @returns {RegExp} Copy of the provided regex, possibly with modified
*                   flags.
*/
function $copyRegExp(regex, options) {
var flags,
opts,
rx;


if (options !== null && typeof options === 'object') {
opts = options;
} else {
opts = {};
}


// Get native flags in use
flags = getNativeFlags.exec($toString(regex))[1];
flags = $onlyCoercibleToString(flags);
if (opts.add) {
flags += opts.add;
flags = flags.replace(clipDups, '');
}


if (opts.remove) {
// Would need to escape `options.remove` if this was public
rx = new RegExp('[' + opts.remove + ']+', 'g');
flags = flags.replace(rx, '');
}


return new RegExp(regex.source, flags);
}


/**
* The abstract operation ToLength converts its argument to an integer
* suitable for use as the length of an array-like object.
*
* @private
* @function
* @param {*} inputArg The object to be converted to a length.
* @returns {number} If len <= +0 then +0 else if len is +INFINITY then
*                   2^53-1 else min(len, 2^53-1).
* @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
*/
function $toLength(inputArg) {
return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
}


/**
* Copies a regex object so that it is suitable for use with searchOf and
* searchLastOf methods.
*
* @private
* @function
* @param {RegExp} regex Regex to copy.
* @returns {RegExp}
*/
function $toSearchRegExp(regex) {
return $copyRegExp(regex, {
add: 'g',
remove: 'y'
});
}


/**
* Returns true if the operand inputArg is a member of one of the types
* Undefined, Null, Boolean, Number, Symbol, or String.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
* @see https://goo.gl/W68ywJ
* @see https://goo.gl/ev7881
*/
function $isPrimitive(inputArg) {
var type = typeof inputArg;


return type === 'undefined' ||
inputArg === null ||
type === 'boolean' ||
type === 'string' ||
type === 'number' ||
type === 'symbol';
}


/**
* The abstract operation converts its argument to a value of type Object
* but fixes some environment bugs.
*
* @private
* @function
* @param {*} inputArg The argument to be converted to an object.
* @throws {TypeError} If inputArg is not coercible to an object.
* @returns {Object} Value of inputArg as type Object.
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.9
*/
function $toObject(inputArg) {
var object;


if ($isPrimitive($requireObjectCoercible(inputArg))) {
object = Object(inputArg);
} else {
object = inputArg;
}


return object;
}


/**
* Converts a single argument that is an array-like object or list (eg.
* arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap
* (used by attributes property)) into a new Array() and returns it.
* This is a partial implementation of the ES6 Array.from
*
* @private
* @function
* @param {Object} arrayLike
* @returns {Array}
*/
function $toArray(arrayLike) {
var object = $toObject(arrayLike),
length = $toLength(object.length),
array = [],
index = 0;


array.length = length;
while (index < length) {
array[index] = object[index];
index += 1;
}


return array;
}


if (!String.prototype.searchOf) {
/**
* This method returns the index within the calling String object of
* the first occurrence of the specified value, starting the search at
* fromIndex. Returns -1 if the value is not found.
*
* @function
* @this {string}
* @param {RegExp|string} regex A regular expression object or a String.
*                              Anything else is implicitly converted to
*                              a String.
* @param {Number} [fromIndex] The location within the calling string
*                             to start the search from. It can be any
*                             integer. The default value is 0. If
*                             fromIndex < 0 the entire string is
*                             searched (same as passing 0). If
*                             fromIndex >= str.length, the method will
*                             return -1 unless searchValue is an empty
*                             string in which case str.length is
*                             returned.
* @returns {Number} If successful, returns the index of the first
*                   match of the regular expression inside the
*                   string. Otherwise, it returns -1.
*/
$defineProperty(String.prototype, 'searchOf', {
enumerable: false,
configurable: true,
writable: true,
value: function (regex) {
var str = $onlyCoercibleToString(this),
args = $toArray(arguments),
result = -1,
fromIndex,
match,
rx;


if (!$isRegExp(regex)) {
return String.prototype.indexOf.apply(str, args);
}


if ($toLength(args.length) > 1) {
fromIndex = +args[1];
if (fromIndex < 0) {
fromIndex = 0;
}
} else {
fromIndex = 0;
}


if (fromIndex >= $toLength(str.length)) {
return result;
}


rx = $toSearchRegExp(regex);
rx.lastIndex = fromIndex;
match = rx.exec(str);
if (match) {
result = +match.index;
}


return result;
}
});
}


if (!String.prototype.searchLastOf) {
/**
* This method returns the index within the calling String object of
* the last occurrence of the specified value, or -1 if not found.
* The calling string is searched backward, starting at fromIndex.
*
* @function
* @this {string}
* @param {RegExp|string} regex A regular expression object or a String.
*                              Anything else is implicitly converted to
*                              a String.
* @param {Number} [fromIndex] Optional. The location within the
*                             calling string to start the search at,
*                             indexed from left to right. It can be
*                             any integer. The default value is
*                             str.length. If it is negative, it is
*                             treated as 0. If fromIndex > str.length,
*                             fromIndex is treated as str.length.
* @returns {Number} If successful, returns the index of the first
*                   match of the regular expression inside the
*                   string. Otherwise, it returns -1.
*/
$defineProperty(String.prototype, 'searchLastOf', {
enumerable: false,
configurable: true,
writable: true,
value: function (regex) {
var str = $onlyCoercibleToString(this),
args = $toArray(arguments),
result = -1,
fromIndex,
length,
match,
pos,
rx;


if (!$isRegExp(regex)) {
return String.prototype.lastIndexOf.apply(str, args);
}


length = $toLength(str.length);
if (!$strictEqual(args[1], args[1])) {
fromIndex = length;
} else {
if ($toLength(args.length) > 1) {
fromIndex = $toInteger(args[1]);
} else {
fromIndex = length - 1;
}
}


if (fromIndex >= 0) {
fromIndex = Math.min(fromIndex, length - 1);
} else {
fromIndex = length - Math.abs(fromIndex);
}


pos = 0;
rx = $toSearchRegExp(regex);
while (pos <= fromIndex) {
rx.lastIndex = pos;
match = rx.exec(str);
if (!match) {
break;
}


pos = +match.index;
if (pos <= fromIndex) {
result = pos;
}


pos += 1;
}


return result;
}
});
}
}());


(function () {
'use strict';


/*
* testing as follow to make sure that at least for one character regexp,
* the result is the same as if we used indexOf
*/


var pre = document.getElementById('out');


function log(result) {
pre.appendChild(document.createTextNode(result + '\n'));
}


function test(str) {
var i = str.length + 2,
r,
a,
b;


while (i) {
a = str.indexOf('a', i);
b = str.searchOf(/a/, i);
r = ['Failed', 'searchOf', str, i, a, b];
if (a === b) {
r[0] = 'Passed';
}


log(r);
a = str.lastIndexOf('a', i);
b = str.searchLastOf(/a/, i);
r = ['Failed', 'searchLastOf', str, i, a, b];
if (a === b) {
r[0] = 'Passed';
}


log(r);
i -= 1;
}
}


/*
* Look for the a among the xes
*/


test('xxx');
test('axx');
test('xax');
test('xxa');
test('axa');
test('xaa');
test('aax');
test('aaa');
}());
<pre id="out"></pre>

如果您正在使用RegExp寻找一个非常简单的lastIndex查找,并且不关心它是否完全模仿了lastIndexOf,那么这可能会引起您的注意。

我只是将字符串反向,并从length - 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;
};

我使用了String.prototype.match(regex),它返回一个字符串数组,包含字符串中给定的regex的所有匹配项(更多信息在这里看到的):

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

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

对于一个比大多数其他答案更简洁的解决方案,你可能想要使用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

在这里,重要的是要包括&;修饰符,以便对所有发生的事件进行计算。如果没有找到正则表达式,这些版本也会导致-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循环更容易解释。

let regExp; // your RegExp here
arr.map(x => !!x.toString().match(regExp)).indexOf(true)

来自Jason Bunting的regexIndexOf可以更简单地反向,并且仍然支持UTF8字符:

function regexLastIndexOf(string, regex, startpos=0) {
return text.length - regexIndexOf([...text].reverse().join(""), regex, startpos) - 1;
}

你可以使用String.prototype.matchAll()和方便的Array.prototype.at():

const str = "foo a foo B";
const matches = [...str.matchAll(/[abc]/gi)];


if (matches.length) {
const indexOfFirstMatch = matches.at(0).index;
const indexOfLastMatch = matches.at(-1).index;
console.log(indexOfFirstMatch, indexOfLastMatch)
}