Есть ли версия JavaScript String.indexOf (), которая позволяет использовать регулярные выражения?

Есть ли в 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');
тест ('ааа');

| внутри [ ] соответствует буквальному символу |. Вы, наверное, имели в виду [abc].
Markus Jarderot 08.11.2008 01:51

да спасибо вы правы, я исправлю но само регулярное выражение не имеет значения ...

Pat 08.11.2008 02:37

Обновил мой ответ Пэт, спасибо за любой отзыв.

Jason Bunting 08.11.2008 22:44

Я нашел более простой и эффективный подход - просто использовать string.match (/ [A-Z] /). Если их мало, метод возвращает null, в противном случае вы получаете объект, вы можете выполнить match (/ [A-Z] /). Index, чтобы получить индекс первой заглавной буквы.

Syler 26.03.2019 22:14
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
233
4
173 534
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

Вы можете использовать substr.

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

Из известной книги по JavaScript, опубликованной О'Рейли: «substr не стандартизирована ECMAScript и поэтому не рекомендуется». Но мне нравится основная идея того, к чему вы клоните.

Jason Bunting 08.11.2008 02:50

Это не проблема. Если вы ДЕЙСТВИТЕЛЬНО обеспокоены этим, используйте вместо этого String.substring () - вам просто нужно выполнить вычисления немного по-другому. Кроме того, JavaScript не должен на 100% зависеть от своего родительского языка.

Peter Bailey 08.11.2008 02:59

Это не обычная проблема - если ваш код работает с реализацией, которая не реализует substr, потому что они хотят придерживаться стандартов ECMAScript, у вас возникнут проблемы. Конечно, заменить его на подстроку не так сложно, но хорошо знать об этом.

Jason Bunting 08.11.2008 03:36

В тот момент, когда у вас возникают проблемы, у вас есть очень-очень простые решения. Я думаю, что комментарии разумны, но голосование против было педантичным.

VoronoiPotato 28.03.2013 17:15

Не могли бы вы отредактировать свой ответ, чтобы предоставить рабочий демонстрационный код?

vsync 12.06.2018 01:08

Экземпляры конструктора 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 () принимает регулярное выражение в качестве параметра, он не позволяет мне указать второй аргумент!

Pat 08.11.2008 01:17

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

Glenn 08.11.2008 06:01

Отличное решение, но результат немного другой. indexOf вернет число с начала (независимо от смещения), тогда как это вернет позицию из смещения. Итак, для паритета вам понадобится что-то вроде этого: function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if (initial >= 0) { initial += offset; } return initial; }

gkoberger 11.08.2014 02:11

Это не изначально, но вы, безусловно, можете добавить эту функциональность

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

Я не тестировал эти методы полностью, но, похоже, пока они работают.

Обновлено для обработки этих случаев

Peter Bailey 08.11.2008 01:57

каждый раз, когда я собираюсь принять этот ответ, я нахожу новый случай! Это дает разные результаты! предупреждение ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);

Pat 08.11.2008 02:20

ну, конечно, они есть - str.lastIndexOf будет выполнять приведение типов к шаблону, преобразовывая его в строку. Строка «/ [d] /» наверняка не найдена во входных данных, поэтому возвращаемый -1 действительно точен.

Peter Bailey 08.11.2008 02:29

Понятно. Прочитав спецификацию String.lastIndexOf () - я просто неправильно понял, как работает этот аргумент. Эта новая версия должна справиться с этим.

Peter Bailey 08.11.2008 02:39

Что-то еще не то, но уже поздно ... Попробую получить тест-кейс, а может утром поправлю. Приносим извинения за беспокойство.

Pat 08.11.2008 02:55

Да - я вижу фатальный недостаток в моем подходе к regexLastIndexOf (), который решение MizardX работает лучше. Я посмотрю, смогу ли я сколотить что-нибудь, что инкапсулирует все это

Peter Bailey 08.11.2008 03:05

Я просто добавил тестовую функцию к вопросу ... это не проходит этот тест (среди прочего) 'axx'.lastIndexOf (' a ', 1)! =' Axx'.regexLastIndexOf (/ a /, 1)

Pat 08.11.2008 15:05

На основе ответа 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) ... Я изучаю его, чтобы узнать, могу ли я исправить эти случаи

Pat 08.11.2008 15:09

Ну, поскольку вы просто хотите сопоставить позицию символ, регулярное выражение, возможно, излишне.

Я предполагаю, что все, что вам нужно, это вместо «найти первым из этих символов» просто найти первый из этих символов.

Это, конечно, простой ответ, но он делает то, что намеревается сделать ваш вопрос, хотя и без части регулярного выражения (потому что вы не пояснили, почему именно это должно быть регулярное выражение)

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

Просто комментарий о патче обезьяны - хотя я знаю о его проблемах - вы думаете, что загрязнять глобальное пространство имен лучше? Это не значит, что конфликты символов в ОБЕИХ случаях не могут произойти, и в основном они реорганизуются / исправляются таким же образом в случае возникновения проблемы.

Peter Bailey 08.11.2008 02:56

Что ж, мне нужно найти \ s, а в некоторых случаях \ W, и я надеялся, что мне не пришлось перечислять все возможности.

Pat 08.11.2008 03:00

BaileyP: эту проблему можно обойти без загрязнения глобального пространства имен, например: см. JQuery. используйте эту модель. один объект для проекта, внутри него все ваши вещи. Mootools оставил неприятный привкус во рту.

Kent Fredric 08.11.2008 09:33

Также следует отметить, что я никогда не кодирую так, как написал там. пример был упрощен по причинам варианта использования.

Kent Fredric 08.11.2008 09:36
Ответ принят как подходящий

Комбинируя несколько уже упомянутых подходов (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 вернет только индекс последнего совпадения неперекрывающийся.

Markus Jarderot 08.11.2008 06:32

Извините, я не ОГРОМНЫЙ парень с регулярными выражениями - вы можете привести мне пример, который заставил бы меня потерпеть неудачу? Я ценю возможность узнать больше, но ваш ответ не поможет такому невежеству, как я. :)

Jason Bunting 08.11.2008 07:40

Джейсон: Я просто добавил функцию для проверки в вопросе. это не удается (среди других тестов) следующий 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)

Pat 08.11.2008 15:02

Хорошо, я прошел этот тест и потратил больше времени на поиск соответствующих деталей.

Jason Bunting 08.11.2008 22:33

"ааааа" .regexLastIndexOf (/ ааа /). Он найдет первые три а, а затем попытается снова сопоставить последние два, что не удастся. "aaaaa" .lastIndexOf ("aaa") находит последние три a.

Markus Jarderot 09.11.2008 01:37

Ах - попался. Что ж, я на этом закончил - у меня нет времени делать что-либо дальше. :( Это было весело.

Jason Bunting 09.11.2008 02:03

Неважно, я сделал еще один удар. :) Больше отзывов приветствуются.

Jason Bunting 09.11.2008 02:13

Наконец-то у меня появилось время протестировать предлагаемые решения, и ваше решение оказалось лучше, поэтому я принимаю его на данный момент.

Pat 01.11.2009 21:27

Думаю, эффективнее использовать regex.lastIndex = result.index + 1; вместо regex.lastIndex = ++nextStop;. Надеюсь, он перейдет к следующему матчу намного быстрее без потери результата.

Gedrox 30.05.2012 13:32

@Gedrox Да, я думаю, что он имеет квадратичную временную сложность без вашего предложения, когда он может иметь линейную сложность, если RegExp достаточно короткий.

user1537366 16.12.2014 12:42

Как насчет ситуации, когда строка содержит несколько объектов JSON или несколько частей, заполняющих регулярное выражение?

TeraTon 16.04.2015 15:56

Если вы предпочитаете извлекать его из npm, эти две служебные функции теперь находятся в NPM как: npmjs.com/package/index-of-regex

Capaj 26.10.2016 22:40

После того, как все предложенные решения так или иначе провалили мои тесты (редактировать: некоторые были обновлены для прохождения тестов после того, как я написал это), я нашел реализацию 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)

Я буду следить за этим вопросом, если кто-то предоставит лучшую / более быструю / более чистую / более общую реализацию, которая работает с любым регулярным выражением.

Вау, это длинный фрагмент кода. Пожалуйста, проверьте мой обновленный ответ и оставьте отзыв. Спасибо.

Jason Bunting 08.11.2008 22:38

Эта реализация нацелена на абсолютную совместимость с lastIndexOf в Firefox и движком SpiderMonkey JavaScript, в том числе в нескольких случаях, которые, возможно, являются крайними случаями. [...] в реальных приложениях вы можете рассчитывать с помощью менее сложного кода, если проигнорируете эти случаи.

Pat 09.11.2008 01:26

Сформируйте страницу Mozilla :-) Я просто взял код и изменил две строчки, оставив все крайние случаи. Поскольку несколько других ответов были обновлены для прохождения тестов, я попробую протестировать их и принять наиболее эффективный. Когда у меня будет время вернуться к этому вопросу.

Pat 09.11.2008 01:48

Я обновил свое решение и ценю любые отзывы или вещи, которые приводят к его сбою. Я внес изменения, чтобы исправить проблему перекрытия, указанную MizardX (надеюсь!)

Jason Bunting 09.11.2008 02:20

Экземпляры 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;
}

Здесь есть несколько серьезных проблем:

  1. совпадающие совпадения не отображаются
  2. возвращаемый индекс предназначен для конца совпадения, а не для начала (отлично, если ваше регулярное выражение является константой)

Но с другой стороны, это намного меньше кода. Для регулярного выражения постоянной длины, которое не может перекрываться (например, /\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.

Robert Koritnik 03.04.2015 17:35

@RobertKoritnik - Я отредактировал свой ответ для поддержки startIndex (или fromIndex). Надеюсь, это поможет!

pmrotule 06.04.2015 10:19
lastIndexOfRegex также должен добавить к результату значение fromIndex.
Peter 20.03.2018 18:49

Ваш алгоритм развалится по следующему сценарию: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi')); Результатом будет 1, когда должно быть 7, потому что indexOf будет искать впервые, когда появляется «romeo», независимо от того, стоит оно в начале слова или нет.

KoralK 02.05.2020 15:49

Для данных с разреженными совпадениями использование 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 раз быстрее, чем принятое решение. Основные моменты:

  1. Он вызывает exec в переданном регулярном выражении, чтобы убедиться, что есть совпадение, или преждевременно завершить работу. Я делаю это с помощью (? = Аналогичным способом, но в IE проверка с помощью exec выполняется значительно быстрее.
  2. Он создает и кэширует измененное регулярное выражение в формате '(r) .(?!.? R)'
  3. Новое регулярное выражение выполняется, и возвращаются результаты либо от этого, либо от первого выполнения;

    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 методов

Я не понимаю цели тестов наверху. Ситуации, требующие регулярного выражения, невозможно сравнить с вызовом 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;
}

Можете ли вы предоставить тест, который заставит мой провалиться? Если вы обнаружили, что это не работает, предоставьте тестовый пример, зачем просто говорить «не работает» и предлагать неоптимальное решение?

Jason Bunting 10.11.2015 23:37

Ооо, мальчик. Вы совершенно правы. Я должен был привести пример. К сожалению, я перешел от этого кода несколько месяцев назад и понятия не имею, в чем заключалась ошибка. : - /

Eli 12.11.2015 01:00

ну такова жизнь. :)

Jason Bunting 18.11.2015 00:45

Использовать:

str.search(regex)

См. Документацию здесь.

@OZZIE: Нет, не совсем. Это в основном Ответ Гленна (с ~ 150 голосами за), за исключением того, что у него вообще есть Никаких объяснений, стартовая позиция не поддерживается отлична от 0, и он был отправлен ... семь лет позже.

ccjmne 03.06.2018 20:00

По-прежнему нет собственных методов, выполняющих запрошенную задачу.

Вот код, который я использую. Он имитирует поведение методов String.prototype.indexOf и String.prototype.lastIndexOf, но они также принимают RegExp в качестве аргумента поиска в дополнение к строке, представляющей значение для поиска.

Да, это довольно длинный ответ, поскольку он пытается как можно точнее следовать текущим стандартам и, конечно же, содержит разумное количество комментариев JSDOC. Однако после минификации размер кода составляет всего 2,27 КБ, а после сжатия для передачи - всего 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>

Если вы ищете очень простой поиск 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

Другие вопросы по теме