Есть ли в javascript эквивалент String.indexOf (), который принимает регулярное выражение вместо строки для первого первого параметра, но при этом позволяет использовать второй параметр?
Мне нужно сделать что-то вроде
str.indexOf(/[abc]/ , i);
и
str.lastIndexOf(/[abc]/ , i);
Хотя String.search () принимает регулярное выражение в качестве параметра, он не позволяет мне указать второй аргумент!
Обновлено:
Это оказалось сложнее, чем я первоначально думал, поэтому я написал небольшую тестовую функцию для проверки всех предоставленных решений ... предполагается, что 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
// Ищем a среди xes
test ('xxx');
test ('axx');
test ('xax');
test ('xxa');
test ('axa');
test ('xaa');
test ('aax');
тест ('ааа');
да спасибо вы правы, я исправлю но само регулярное выражение не имеет значения ...
Обновил мой ответ Пэт, спасибо за любой отзыв.
Я нашел более простой и эффективный подход - просто использовать string.match (/ [A-Z] /). Если их мало, метод возвращает null, в противном случае вы получаете объект, вы можете выполнить match (/ [A-Z] /). Index, чтобы получить индекс первой заглавной буквы.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете использовать substr.
str.substr(i).match(/[abc]/);
Из известной книги по JavaScript, опубликованной О'Рейли: «substr не стандартизирована ECMAScript и поэтому не рекомендуется». Но мне нравится основная идея того, к чему вы клоните.
Это не проблема. Если вы ДЕЙСТВИТЕЛЬНО обеспокоены этим, используйте вместо этого String.substring () - вам просто нужно выполнить вычисления немного по-другому. Кроме того, JavaScript не должен на 100% зависеть от своего родительского языка.
Это не обычная проблема - если ваш код работает с реализацией, которая не реализует substr, потому что они хотят придерживаться стандартов ECMAScript, у вас возникнут проблемы. Конечно, заменить его на подстроку не так сложно, но хорошо знать об этом.
В тот момент, когда у вас возникают проблемы, у вас есть очень-очень простые решения. Я думаю, что комментарии разумны, но голосование против было педантичным.
Не могли бы вы отредактировать свой ответ, чтобы предоставить рабочий демонстрационный код?
Экземпляры конструктора String имеют .search() метод, который принимает 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;
}
из вопроса: Хотя String.search () принимает регулярное выражение в качестве параметра, он не позволяет мне указать второй аргумент!
str.substr (i) .search (/ re /)
Отличное решение, но результат немного другой. indexOf вернет число с начала (независимо от смещения), тогда как это вернет позицию из смещения. Итак, для паритета вам понадобится что-то вроде этого: function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if (initial >= 0) { initial += offset; } return initial; }
Это не изначально, но вы, безусловно, можете добавить эту функциональность
<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>
Я не тестировал эти методы полностью, но, похоже, пока они работают.
Обновлено для обработки этих случаев
каждый раз, когда я собираюсь принять этот ответ, я нахожу новый случай! Это дает разные результаты! предупреждение ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
ну, конечно, они есть - str.lastIndexOf будет выполнять приведение типов к шаблону, преобразовывая его в строку. Строка «/ [d] /» наверняка не найдена во входных данных, поэтому возвращаемый -1 действительно точен.
Понятно. Прочитав спецификацию String.lastIndexOf () - я просто неправильно понял, как работает этот аргумент. Эта новая версия должна справиться с этим.
Что-то еще не то, но уже поздно ... Попробую получить тест-кейс, а может утром поправлю. Приносим извинения за беспокойство.
Да - я вижу фатальный недостаток в моем подходе к regexLastIndexOf (), который решение MizardX работает лучше. Я посмотрю, смогу ли я сколотить что-нибудь, что инкапсулирует все это
Я просто добавил тестовую функцию к вопросу ... это не проходит этот тест (среди прочего) 'axx'.lastIndexOf (' a ', 1)! =' Axx'.regexLastIndexOf (/ a /, 1)
На основе ответа BaileyP. Основное отличие состоит в том, что эти методы возвращают -1, если шаблон не может быть сопоставлен.
Редактировать: Благодаря ответу Джейсона Бантинга у меня появилась идея. Почему бы не изменить свойство .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;
}
На данный момент это кажется наиболее многообещающим (после нескольких исправлений синтаксиса) :-) Только провал нескольких тестов на граничных условиях. Такие вещи, как 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Я изучаю его, чтобы узнать, могу ли я исправить эти случаи
Ну, поскольку вы просто хотите сопоставить позицию символ, регулярное выражение, возможно, излишне.
Я предполагаю, что все, что вам нужно, это вместо «найти первым из этих символов» просто найти первый из этих символов.
Это, конечно, простой ответ, но он делает то, что намеревается сделать ваш вопрос, хотя и без части регулярного выражения (потому что вы не пояснили, почему именно это должно быть регулярное выражение)
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
Просто комментарий о патче обезьяны - хотя я знаю о его проблемах - вы думаете, что загрязнять глобальное пространство имен лучше? Это не значит, что конфликты символов в ОБЕИХ случаях не могут произойти, и в основном они реорганизуются / исправляются таким же образом в случае возникновения проблемы.
Что ж, мне нужно найти \ s, а в некоторых случаях \ W, и я надеялся, что мне не пришлось перечислять все возможности.
BaileyP: эту проблему можно обойти без загрязнения глобального пространства имен, например: см. JQuery. используйте эту модель. один объект для проекта, внутри него все ваши вещи. Mootools оставил неприятный привкус во рту.
Также следует отметить, что я никогда не кодирую так, как написал там. пример был упрощен по причинам варианта использования.
Комбинируя несколько уже упомянутых подходов (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(). Пожалуйста, дайте мне знать, если он по-прежнему не работает и при каких обстоятельствах.
ОБНОВЛЕНИЕ: Проходит все тесты, найденные в комментариях на этой странице, и мои собственные. Конечно, это не значит, что он пуленепробиваемый. Любая обратная связь приветствуется.
Ваш regexLastIndexOf вернет только индекс последнего совпадения неперекрывающийся.
Извините, я не ОГРОМНЫЙ парень с регулярными выражениями - вы можете привести мне пример, который заставил бы меня потерпеть неудачу? Я ценю возможность узнать больше, но ваш ответ не поможет такому невежеству, как я. :)
Джейсон: Я просто добавил функцию для проверки в вопросе. это не удается (среди других тестов) следующий 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)
Хорошо, я прошел этот тест и потратил больше времени на поиск соответствующих деталей.
"ааааа" .regexLastIndexOf (/ ааа /). Он найдет первые три а, а затем попытается снова сопоставить последние два, что не удастся. "aaaaa" .lastIndexOf ("aaa") находит последние три a.
Ах - попался. Что ж, я на этом закончил - у меня нет времени делать что-либо дальше. :( Это было весело.
Неважно, я сделал еще один удар. :) Больше отзывов приветствуются.
Наконец-то у меня появилось время протестировать предлагаемые решения, и ваше решение оказалось лучше, поэтому я принимаю его на данный момент.
Думаю, эффективнее использовать regex.lastIndex = result.index + 1; вместо regex.lastIndex = ++nextStop;. Надеюсь, он перейдет к следующему матчу намного быстрее без потери результата.
@Gedrox Да, я думаю, что он имеет квадратичную временную сложность без вашего предложения, когда он может иметь линейную сложность, если RegExp достаточно короткий.
Как насчет ситуации, когда строка содержит несколько объектов JSON или несколько частей, заполняющих регулярное выражение?
Если вы предпочитаете извлекать его из npm, эти две служебные функции теперь находятся в NPM как: npmjs.com/package/index-of-regex
После того, как все предложенные решения так или иначе провалили мои тесты (редактировать: некоторые были обновлены для прохождения тестов после того, как я написал это), я нашел реализацию Mozilla для Array.indexOf и Array.lastIndexOf
Я использовал их для реализации моей версии 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)
Я буду следить за этим вопросом, если кто-то предоставит лучшую / более быструю / более чистую / более общую реализацию, которая работает с любым регулярным выражением.
Вау, это длинный фрагмент кода. Пожалуйста, проверьте мой обновленный ответ и оставьте отзыв. Спасибо.
Эта реализация нацелена на абсолютную совместимость с lastIndexOf в Firefox и движком SpiderMonkey JavaScript, в том числе в нескольких случаях, которые, возможно, являются крайними случаями. [...] в реальных приложениях вы можете рассчитывать с помощью менее сложного кода, если проигнорируете эти случаи.
Сформируйте страницу Mozilla :-) Я просто взял код и изменил две строчки, оставив все крайние случаи. Поскольку несколько других ответов были обновлены для прохождения тестов, я попробую протестировать их и принять наиболее эффективный. Когда у меня будет время вернуться к этому вопросу.
Я обновил свое решение и ценю любые отзывы или вещи, которые приводят к его сбою. Я внес изменения, чтобы исправить проблему перекрытия, указанную MizardX (надеюсь!)
Экземпляры 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 of я использую отрицательный прогноз, чтобы найти последнее вхождение, если только 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.info(arr);
arr.regexIndexOf(/\d/, 4);
В некоторых простых случаях вы можете упростить обратный поиск, используя разделение.
function regexlast(string,re){
var tokens=string.split(re);
return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}
Здесь есть несколько серьезных проблем:
Но с другой стороны, это намного меньше кода. Для регулярного выражения постоянной длины, которое не может перекрываться (например, /\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);
Это действительно хороший трюк. Было бы здорово, если бы вы расширили его, чтобы он также принимал параметр startIndex, как обычно это делают indeoxOf и lastIndexOf.
@RobertKoritnik - Я отредактировал свой ответ для поддержки startIndex (или fromIndex). Надеюсь, это поможет!
lastIndexOfRegex также должен добавить к результату значение fromIndex.
Ваш алгоритм развалится по следующему сценарию: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi')); Результатом будет 1, когда должно быть 7, потому что indexOf будет искать впервые, когда появляется «romeo», независимо от того, стоит оно в начале слова или нет.
Для данных с разреженными совпадениями использование 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;
}
Для плотных данных я сделал это. Это сложно по сравнению с методом execute, но для плотных данных он в 2-10 раз быстрее, чем любой другой метод, который я пробовал, и примерно в 100 раз быстрее, чем принятое решение. Основные моменты:
Новое регулярное выражение выполняется, и возвращаются результаты либо от этого, либо от первого выполнения;
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;
};
Я не понимаю цели тестов наверху. Ситуации, требующие регулярного выражения, невозможно сравнить с вызовом indexOf, что, как мне кажется, является целью создания метода в первую очередь. Чтобы тест прошел, имеет больше смысла использовать «xxx + (?! x)», чем настраивать способ итерации регулярного выражения.
Последний указатель Джейсона Бантинга не работает. Моя не оптимальна, но работает.
//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)
См. Документацию здесь.
@OZZIE: Нет, не совсем. Это в основном Ответ Гленна (с ~ 150 голосами за), за исключением того, что у него вообще есть Никаких объяснений, стартовая позиция не поддерживается отлична от 0, и он был отправлен ... семь лет позже.
По-прежнему нет собственных методов, выполняющих запрошенную задачу.
Вот код, который я использую. Он имитирует поведение методов String.prototype.indexOf и String.prototype.lastIndexOf, но они также принимают RegExp в качестве аргумента поиска в дополнение к строке, представляющей значение для поиска.
Да, это довольно длинный ответ, поскольку он пытается как можно точнее следовать текущим стандартам и, конечно же, содержит разумное количество комментариев JSDOC. Однако после минификации размер кода составляет всего 2,27 КБ, а после сжатия для передачи - всего 1023 байта.
Два метода, которые это добавляет к String.prototype (с использованием Object.defineProperty, где доступно):
searchOfsearchLastOfОн проходит все тесты, опубликованные 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>Если вы ищете очень простой поиск lastIndex с помощью RegExp и не заботитесь о том, имитирует ли он 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;
};
Я использовал 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.info(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g, 10));
// expect 29
console.info(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
|внутри[ ]соответствует буквальному символу|. Вы, наверное, имели в виду[abc].