Изменение флагов 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 мс на всем огромном «451 градусе по Фаренгейту» Рэя Брэдбери, ища все «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, или это может быть совершенно другое форматирование. И с самого начала это кажется плохой практикой кодирования.


person Witiko    schedule 29.04.2011    source источник
comment
Я понимаю. Ну, я отредактировал пост, чтобы можно было убрать даунрейт. :-)   -  person Witiko    schedule 29.04.2011
comment
Сделано и сделано. Прости за это!   -  person ridgerunner    schedule 29.04.2011


Ответы (4)


Во-первых, ваш текущий код работает неправильно, когда 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;
};
person ridgerunner    schedule 29.04.2011
comment
Спасибо, что указали на несоответствие. Мне нравится решение, возможно, безопаснее не расширять прототип RegExp функцией флагов (как было предложено выше). - person Witiko; 29.04.2011
comment
А еще лучше используйте myRegex.test(str), если это все, что вам нужно. Это и короче, и быстрее. - person Phrogz; 30.04.2011
comment
Не совсем, я пытаюсь сосчитать все случаи. :-) - person Witiko; 30.04.2011

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

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

var regex = /find/;
var other = new RegExp(regex.source, "gm");
alert(other.global);
alert(other.multiline);
person David Ruttka    schedule 29.04.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');
person ChaosPandion    schedule 29.04.2011
comment
Я понял часть .source всего несколько минут назад. Я, однако, совершенно забыл о том, что флаги не являются частью исходного кода. :) Думаю воплотить идею. Спасибо ;) - person Witiko; 29.04.2011

person    schedule
comment
Используйте примечание: () вокруг r.global ? "" : "g" необходим, потому что условный оператор имеет более низкий приоритет, чем сложение. Это сбило меня с толку, так что будь осторожен. - person AlienKevin; 25.08.2019