1

Тема: JS: test() и exec() таки глючат или нет?

Давным давно, в году эдак в 2010, товарищ jite написал вот тут:

jite пишет:

Кстати, не рекомендую пользоваться функциями test() и exec() используя регэкспы с флагом global - глючат. Я чуть позже на эту тему для тех кто не в теме начну отдельную тему.

Написал и пропал. Есть тут кто-нибудь, кто владеет той же информацией что и глубокоуважаемый вагоноуважатый - jite?
Интересно ж.

Нас невозможно сбить с пути, нам пофигу куда идти.

2

Re: JS: test() и exec() таки глючат или нет?

Можно написать коллеге jite и задать этот вопрос.

3

Re: JS: test() и exec() таки глючат или нет?

Уже пол года он не пишет тут ничего, или клава сломалась и он только форум просматривает, или вообще сюда не ходит.

Нас невозможно сбить с пути, нам пофигу куда идти.

4

Re: JS: test() и exec() таки глючат или нет?

Я флаг глобал не ставил. test - еще ни разу (из 3) не глючил. Возможно корректно работает из-за моей простой и частной задачи или лицензионной ОСи... Один раз exec применил - то же самое, все в порядке.

У меня - не глючат.

Любители построили Ковчег, а профессионалы - Титаник.

5

Re: JS: test() и exec() таки глючат или нет?

Не думаю,  что jite имел ввиду баги во всех 100% случаев. Видимо ему попадались какие то хитровыдуманные куски кода, в которых были проблемы.

Нас невозможно сбить с пути, нам пофигу куда идти.

6

Re: JS: test() и exec() таки глючат или нет?

DnsIs пишет:

Уже пол года он не пишет тут ничего, или клава сломалась и он только форум просматривает, или вообще сюда не ходит.

Я про то и говорю — напишите письмо.

7 (изменено: Rumata, 2012-03-03 15:05:38)

Re: JS: test() и exec() таки глючат или нет?

Коллеги, уверяю Вас - он заблуждается. Это прекрасные методы для глобального поиска всех соответствий заданному шаблону. В общем, метод .text() можно рассматривать как частный случай метода .exec():

if ( re.test(string) ) 

if ( re.exec(string) != null )

Пример 1.

В качестве примера можете глянуть на модуль обработки INI файлов:


var text = 'abc\ndef\n\n\nklmn\n\nxyz\n\n\n';

var re = /[^\r\n]+/g;

var r;
while ( r = re.exec(text) ) {
	alert(r);
}

В текстовой переменной text ищутся "значащие" подстроки, то есть содержащие любые символы кроме символов перевода строк (\r\n - в windows, \n - в unix). В переменной r возвращается массив совпадений (0 - найденная подстрока, 1 - совпадение в первой скобке, 2 - совпадение во второй скобке и т.д.). Найденное значение используется в дальнейших вычислениях. А повторный поиск ведется с запомненной позиции RegExp.lastIndex. Когда все совпадения исчерпаны, метод вернет значение null, а цикл завершит свою работу, так как while null преобразуется в while false, что приведет к завершению цикла.

То что уважаемый jite подразумевает под "глючностью" является особенностью методов. Так как при включенной "глобальности" при сопоставлении происходит обновления всех свойств объекта, а так же и свойств объекта RegExp. Посмотрите модифицированный пример:


var text = 'abc\ndef\n\n\nklmn\n\nxyz\n\n\n';

var re = /([^\r\n]+)/g;

var r;
while ( r = re.exec(text) ) {
	alert('index: ' + r.index);
	alert('index: ' + RegExp.index);
	alert('last index: ' + r.lastIndex);
	alert('last index: ' + RegExp.lastIndex);
	alert(r[1]);
	alert(RegExp.$1);
}

Если вы активно используете свойства объекта RegExp, то можете столкнутся с проблемой использования нескольких регулярных выражений, когда они перезаписывают результат выполнения.

Пример 2.

Но и это не все. Вот пример:


var s = '123abc456def789'
var t = 'abcdef1111111'

var re = /\d+/g

re.exec(s); // ['123']
re.exec(t); // ['1111111']
re.exec(s); // ['89']

В последней строке возвращается неполное совпадение.

Вот два простых совета
1. Не надейтесь на свойства RegExp, а используйте результат возвращенный методом.
2. Не меняйте контекст глобального поиска.

( 2 * b ) || ! ( 2 * b )

8

Re: JS: test() и exec() таки глючат или нет?

Честно говоря, про слова jite я вспомнил после статьи на хабре - "регулярная ловушка" и мне подумалось, что он как раз таки хотел рассказать нам про аналогичные случаи. Но вы Rumata проницательны. 8)

Нужно просто более подробно вникнуть в суть javascript методов exec, search, test, match и точно знать чем они отличаются. Пойду ка перечитаю спецификацию.

Нас невозможно сбить с пути, нам пофигу куда идти.

9

Re: JS: test() и exec() таки глючат или нет?

Всем спасибо.

Вот только грабли это, или всё правильно?
Думаю всё правильно, нужно знать и уметь пользоваться. Просто на хабре сделали проблему вместо разъяснений реальности не очевидной для большинства. ИМХО.
Я бы такое долго искал, теперь главное вовремя про это вспомнить.:)

Ну и мои три копейки, на газировку с сиропом в автомате.

проверяет регулярные выражения
http://www.pagecolumn.com/tool/regtest.htm

10

Re: JS: test() и exec() таки глючат или нет?

Мне больше понравился http://regexpres.narod.ru/calculator.html

11

Re: JS: test() и exec() таки глючат или нет?

Любитель, Serge Yolkin, используйте тэг «url» для оформления ссылок на форуме.

12 (изменено: Rumata, 2012-03-03 15:42:48)

Re: JS: test() и exec() таки глючат или нет?

DnsIs пишет:

суть javascript методов exec, search, test, match и точно знать чем они отличаются

сопоставление строк заданному шаблону. Одни - методы экземпляров объекта RegExp, другие - String. Методов не так много.

Обозначения

re - регулярное выражение
str - строка
r, m - результаты сопоставления

.match(), .exec()


m = str.match(re)
r = re.exec(str)

Сопоставить и вернуть массив найденных подстрок или null в случае неудачи

.test()


re.test(str)

// аналоги
re.exec(str) != null
str.match(re) != null

Проверить на соответствие строки шаблону. Результат - true или false

.search()


str.search(re)

Проверить на соответствие строки шаблону и вернуть позицию первой найденной подстроки, или -1, если соответствий нет.

Дополнительно


str.replace(шаблон, замена)

Метод поиска соответствия шаблону и замена найденной подстроки. В качестве шаблона может выступать как строка так и регулярное выражение. Замена - строка, или функция.


str.split(шаблон[, количество])

Разбить строку на массив строк, используя в качестве разделителя шаблон. Необязательный параметр количество определяет количество сохраненных подстрок.

Обратил внимание, что в своем сообщении №7, в самом начале допустил  ошибку. Исправил.

( 2 * b ) || ! ( 2 * b )

13

Re: JS: test() и exec() таки глючат или нет?

Rumata, спасибо за краткую справку, но я хотел сказать, что многие как раз пробегутся то такой вот краткой справке и тонкостей не замечают. Ваше 7 сообщение как раз это и показывает.
Я кстати до прочтения хабро-темы, тоже особо не задумывался и не вникал о работе всех вышеописанных функций. Мне пока хватало пары из их. Но после этой ветки форума, я таки прочту более вдумчиво мануалы.

Мораль сей темы такова, прежде чем писать о багах, сначала убедитесь, что на 100% понимаете как оно работает там. 8)

Нас невозможно сбить с пути, нам пофигу куда идти.

14

Re: JS: test() и exec() таки глючат или нет?

Хабр, конечно, хорошо, но в целом уровень статей там очень низкий.

DnsIs пишет:

убедитесь, что на 100% понимаете

О да, согласен. Но все мы люди и совершать ошибки это наша багофича )

( 2 * b ) || ! ( 2 * b )

15

Re: JS: test() и exec() таки глючат или нет?

А мне кто нить скажет, зачем в test() использовать глобальный флаг? мне не удалось придумать ни чего осмысленного.

16

Re: JS: test() и exec() таки глючат или нет?

Любитель пишет:

мне не удалось придумать ни чего осмысленного

Чтобы искать все соответствия шаблону. Например, так:

var s = '1====2=======,==3,======4=5,=6=======,7=,8';
var re = /(\d+)/g;

while ( re.test(s) ) {
    alert(RegExp.$1);
}
( 2 * b ) || ! ( 2 * b )

17

Re: JS: test() и exec() таки глючат или нет?

Идея понятна, только где такое реально применить?
Не вижу области применения.

18

Re: JS: test() и exec() таки глючат или нет?

Любитель, не могу сказать - не сталкивался с такой задачей. Рассматривайте использование глобального флага в .test() как последствия синонимичности метода вызову метода .exec() != null.

( 2 * b ) || ! ( 2 * b )

19

Re: JS: test() и exec() таки глючат или нет?

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

20

Re: JS: test() и exec() таки глючат или нет?

RegExp.prototype.reset = function() 
{ 
    this.lastIndex = 0;
    return this;
};

RegExp.prototype.forEach = function(str, fun, scope) 
{ 
    this.reset();
    for(var res = null; (res = this.exec(str)) != null;)
       if(fun.apply(scope, res) === false)
          break;
    return this;
};

Пример:

var my_str = "abracadabra 123.123.123.123,456.456.456.456; vinipuxivsevsevse - 789.789.789.789|098.098.098.098 56.56.56.56 traxtibidoxtibidox";

var my_regexp = (/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/g);

my_regexp.forEach(my_str, function(str, first_parentheses)
{
   WScript.Echo(first_parentheses);
});

Вывод соответственно:

123.123.123.123
456.456.456.456
789.789.789.789
098.098.098.098
56.56.56.56

21

Re: JS: test() и exec() таки глючат или нет?

max7
голосую за - RegExp#forEach(), но против - RegExp#reset(), так как не возможно учесть всех нюансов и возможных хотелок. Проще пересоздать регулярное выражение.

( 2 * b ) || ! ( 2 * b )

22 (изменено: Serge Yolkin, 2012-06-22 17:37:59)

Re: JS: test() и exec() таки глючат или нет?

Оказавшись в глубоком оффлайне сваял оффлайновый калькулятор. Горячие кнопы - Ctrl+i(g,m) - вкл/выкл i, g, m. Enter - посчитать. То же мышкой - клик на i, g, m; клик в поле Result. Резалтить можно многократно, пока не изменялся фокус (не кликалось в других полях). При запуске вставляет в поле String содержимое буфера. Чтобы строку руками не набирать. Делалось для себя на скорую руку. Форматирование страшненькое, поскольку работал на недобуке без мыша (экранчик маленький, а елозить по нему тачпадом - то ещё удовольствие).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>RegExp калькулятор</title>
  <meta http-equiv="MSThemeCompatible" content="yes" />
  <meta http-equiv="X-UA-Compatible" content="IE=8" />
  <hta:application id="HTApp" applicationName="ReXc-IXm" border="dialog"
   icon="%WinDir%\System32\calc.exe" innerBorder="no" scroll="no" version="0.9"/>
  <script type="text/jscript" language="JScript">
    self.resizeTo(640,480);
    self.moveTo(200,100);
  </script>
  <style type="text/css">
   *{margin:0;overflow:hidden;padding:0;}
   fieldset{margin:0 6px;padding:2px 1%;white-space:nowrap;}
   legend{color:crimson;cursor:default;padding:0 6px;}
   p{color:navy;margin:3px;text-indent:3px;}
   select,#tv{color:maroon;width:75%;}
   textarea{border:none;font:normal 16px 'Times New Roman';overflow:auto;}
   .sq{color:maroon;font-weight:bold;}
   #bb{font:normal 18px Webdings;height:27px;
    margin-top:-9px;right:10px;width:25px;}
   #bb,#sa,#si,#st,#tv{position:absolute;}
   #be{cursor:default;height:8em;}
   #br{color:green;display:none;}
   #dg,#di,#dm{color:silver;cursor:hand;display:inline;
    font:bold 16px 'Courier New';}
   #dg,#di,#dm,#sx{vertical-align:top;}
   #ds{color:dimgray;margin-top:10px;text-indent:20px;}
   #sa{left:14em;}#si{left:21em;}
   #sr,#sx{font:normal 16px 'Courier New';min-width:0.3em;width:auto;}
   #sr,#sx,#ts{padding:0 2px;}#st,#tv{left:7em;}#si,#sx{color:blue;}
   #ts{color:navy;height:8em;width:100%;}#tv{height:4em;padding:0 4px;}
  </style>
</head>
<body onload="sx.focus();">
  <fieldset onclick="sx.focus();">
    <legend>regExp =</legend><span class="sq">/</span><span id="sx"
     onfocus="this.style.background='none';n=true;" contentEditable></span><span
      class="sq">/</span>
    <div id="di" onclick="fm(this);">i</div><div id="dg"
     onclick="fm(this);" style="margin-left:1px;">g</div><div id="dm"
      onclick="fm(this);" style="margin-left:3px;">m</div><button id="bb"
       title="Копировать regExp в буфер"
        onclick="window.clipboardData.setData('text',fr().toString());"
         hideFocus>¤</button>
  </fieldset>
  <fieldset id="br" onclick="sr.focus();"><legend>Replace
   with:</legend>&emsp;<span id="sr" onfocus="n=true;"
    contentEditable></span></fieldset>
  <fieldset onclick="ts.focus();"><legend>String =</legend><textarea id="ts"
   onfocus="this.style.background='none';n=true;"
    ></textarea></fieldset>
  <div id="ds">Method selection:&emsp;
    <select onchange="fs(this.value);n=true;">
      <optgroup label="&emsp;RegExp Object Methods:">
        <option value="0" >&emsp;Result =
         regExp.exec ( String ) ;</option>
        <option value="1" >&emsp;Result =
         regExp.test ( String ) ;</option>
      </optgroup>
      <optgroup label="&emsp;String Object Methods:">
        <option value="2" >&emsp;Result =
         String.match ( regExp ) ;</option>
        <option value="3" >&emsp;Result =
         String.replace ( regExp, reWith ) ;</option>
        <option value="4" >&emsp;Result =
         String.search ( regExp ) ;</option>
        <option value="5" >&emsp;Result =
         String.split ( regExp ) ;</option>
      </optgroup>
    </select>
  </div>
  <fieldset id="be" onclick="fe();">
    <legend>Result</legend>
    <p>Result type:<span id="st"></span><span id="sa">lastIndex:</span><span
     id="si"></span></p><hr />
    <p>Result value:<textarea id="tv" readOnly></textarea></p>
  </fieldset>
</body>
<script type="text/jscript" language="JScript">
  var m=0,n=true,p,r,s;
  ts.innerText=window.clipboardData.getData('text');
  function fe(){
    if(n){
      st.style.color='blue';
      if(sx.innerText.replace(/\s/g,'')==''){
        sx.innerText='';sx.style.background='red';return;
      }
      r=fr();
      s=ts.innerText;
      n=false;
    }
    switch(m){
     case 0:p=r.exec(s);break;
     case 1:p=r.test(s);break;
     case 2:p=s.match(r);break;
     case 3:p=s.replace(r,sr.innerText);s=p;break;
     case 4:p=s.search(r);break;
     case 5:p=s.split(r);
    }
    try{
      st.innerText=typeof(p)=='object'?'array ('+p.length+')':typeof(p);
      si.innerText=r.lastIndex;
      tv.innerText='['+(typeof(p)=='object'?p.join(']['):p)+']';
    }catch(e){st.style.color='crimson';st.innerText='Error!';tv.innerText=p;}
  }
  function fm(e){
    if(e.style.color=='navy')e.style.color='silver';
    else e.style.color='navy';
  }
  function fr(){
    return(new RegExp(sx.innerText,(di.style.color=='navy'?'i':'')+
     (dg.style.color=='navy'?'g':'')+(dm.style.color=='navy'?'m':'')));
  }
  function fs(e){
    if(e==3)br.style.display='block';
    else br.style.display='none';
    m=~~e;
  }
  document.onkeydown=function(){
    if(event.keyCode==13){fe();return(false);}
    else if(event.ctrlKey){
      if(event.keyCode==73){fm(di);return(false);}
      if(event.keyCode==71){fm(dg);return(false);}
      if(event.keyCode==77){fm(dm);return(false);}
    }
  }
  document.body.onbeforepaste=function(){
    window.clipboardData.setData(
     'text',window.clipboardData.getData('text').replace(
      /(<\/?\w\d?[^>]*>)|\n|\r|\s+/g,' '
     )
    );
  }
</script>
</html>