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

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

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 мс на всем огромном Ray Bradbury 451 Fahrenheit), которые ищут все "the" s).

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

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

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

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

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

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

Ответ 1

Во-первых, ваш текущий код работает некорректно, если 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;
};

Ответ 2

var globalRegex = new RegExp(needle.source, "g");

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

var regex = /find/;
var other = new RegExp(regex.source, "gm");
alert(other.global);
alert(other.multiline);

Ответ 3

Вы не можете многое сделать, но я настоятельно рекомендую вам не использовать 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');