Промяна на флаговете 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, така и базираните на низове търсения са доста сравними по отношение на времето за изпълнение (и двете ~2ms за целия огромен "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, или може да е изцяло различно форматиране. И като начало изглежда като лоша практика за кодиране.


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