Изменение флагов RegExp - PullRequest
       24

Изменение флагов RegExp

14 голосов
/ 29 апреля 2011

Итак, я написал эту функцию, чтобы подсчитать количество вхождений подстроки в строке:

String.prototype.numberOf = function(needle) {
  var num = 0,
      lastIndex = 0;
  if(typeof needle === "string" || needle instanceof String) {
    while((lastIndex = this.indexOf(needle, lastIndex) + 1) > 0)
      {num++;} return num;
  } else if(needle instanceof RegExp) {
    // needle.global = true;
    return this.match(needle).length;
  } return 0;
};

Сам метод работает довольно хорошо, и оба поиска на основе RegExp и String вполне сопоставимы по времени выполнения (оба ~ 2 мс на всем «451 по Фаренгейту» Рэя Брэдбери, ищущего все «the»). 1004 *

Что меня беспокоит, так это невозможность изменить флаг предоставленного экземпляра RegExp. Нет смысла вызывать String.prototype.match в этой функции без глобального флага поставляемого регулярного выражения, установленного в значение true, так как он будет отмечать только первое вхождение. Конечно, вы можете установить флаг вручную для каждого передаваемого в функцию RegExp, однако я бы предпочел иметь возможность клонировать и затем манипулировать флагами поставляемого регулярного выражения.

Удивительно, но мне запрещено это делать, поскольку флаг RegExp.prototype.global (точнее, все флаги) доступен только для чтения. Отсюда закомментированная строка 8.

Итак, мой вопрос: Есть ли хороший способ изменения флагов объекта RegExp?

Я действительно не хочу делать такие вещи:

if(!expression.global)
  expression = eval(expression.toString() + "g");

Некоторые реализации могут не поддерживать событие RegExp.prototype.toString и просто наследовать его от Object.prototype , или это может быть совсем другое форматирование. И это просто кажется плохой практикой кодирования.

Ответы [ 4 ]

14 голосов
/ 29 апреля 2011

Во-первых, ваш текущий код не работает правильно, когда needle является регулярным выражением, которое не соответствует. то есть следующая строка:

return this.match(needle).length;

Метод match возвращает null, когда совпадений нет. Затем генерируется ошибка JavaScript при обращении к (* неудачно) length свойству null. Это легко исправить так:

var m = this.match(needle);
return m ? m.length : 0;

Теперь к проблеме. Вы правы, когда говорите, что global, ignoreCase и multiline только для чтения. Единственный вариант - создать новый RegExp. Это легко сделать, так как исходная строка регулярного выражения хранится в свойстве re.source. Вот проверенная модифицированная версия вашей функции, которая исправляет описанную выше проблему и создает новый объект RegExp, когда needle еще не имеет установленного флага global:

String.prototype.numberOf = function(needle) {
    var num = 0,
    lastIndex = 0;
    if (typeof needle === "string" || needle instanceof String) {
        while((lastIndex = this.indexOf(needle, lastIndex) + 1) > 0)
            {num++;} return num;
    } else if(needle instanceof RegExp) {
        if (!needle.global) {
            // If global flag not set, create new one.
            var flags = "g";
            if (needle.ignoreCase) flags += "i";
            if (needle.multiline) flags += "m";
            needle = RegExp(needle.source, flags);
        }
        var m = this.match(needle);
        return m ? m.length : 0;
    }
    return 0;
};
8 голосов
/ 29 апреля 2011
var globalRegex = new RegExp(needle.source, "g");

Демонстрация в реальном времени РЕДАКТИРОВАТЬ : только для демонстрации того, что вы можете установить несколько модификаторов

var regex = /find/;
var other = new RegExp(regex.source, "gm");
alert(other.global);
alert(other.multiline);
4 голосов
/ 29 апреля 2011

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

RegExp.prototype.flags = function () {
    return (this.ignoreCase ? "i" : "")
        + (this.multiline ? "m" : "")
        + (this.global ? "g" : "");
};

var reg1 = /AAA/i;
var reg2 = new RegExp(reg1.source, reg1.flags() + 'g');
1 голос
/ 15 ноября 2018
r = new Regexp(r.source, r.flags + (r.global ? "" : "g"));
...