Подтвердить что ты не робот

Динамическая версия Inline RegExp в JavaScript

Я наткнулся на этот тест производительности, сказав, что RegExps в JavaScript не обязательно медленный: http://jsperf.com/regexp-indexof-perf

Но есть одна вещь, которую я не понял: два случая связаны с чем-то, что я считаю абсолютно одинаковым:

RegExp('(?:^| )foo(?: |$)').test(node.className);

И

/(?:^| )foo(?: |$)/.test(node.className);

По-моему, эти две строки были в точности одинаковыми, вторая - своего рода сокращение для создания объекта RegExp. Тем не менее, он в два раза быстрее, чем первый.

Эти случаи называются "динамическое регулярное выражение" и "встроенное регулярное выражение".

Может ли кто-нибудь помочь мне понять разницу (и разрыв в производительности) между этими двумя?

4b9b3361

Ответ 1

В настоящее время ответы, приведенные здесь, не являются полностью полными/правильными.

Начиная с ES5, буквальное синтаксическое поведение аналогично синтаксису RegExp() в отношении создания объекта: оба они создают новый объект RegExp каждый раз, когда путь к коду попадает в выражение, в котором они принимают участие.

Следовательно, единственное различие между ними заключается в том, как часто это регулярное выражение компилируется:

  • С буквальным синтаксисом - один раз во время первоначального анализа кода и составление
  • С синтаксисом RegExp() - каждый раз, когда создается новый объект

См., Например, книгу Шаблоны JavaScript Stoyan Stefanov :

Еще одно различие между литералом регулярного выражения и Конструктор состоит в том, что литерал создает объект только один раз за разбор времени. Если вы создаете одно и то же регулярное выражение в цикле, ранее созданный объект будет возвращен со всеми его свойствами (например, lastIndex) уже установлен с первого раза. Рассмотрим Следующий пример в качестве иллюстрации того же объекта вернулся дважды.

function getRE() {
    var re = /[a-z]/;
    re.foo = "bar";
    return re;
}

var reg = getRE(),
    re2 = getRE();

console.log(reg === re2); // true
reg.foo = "baz";
console.log(re2.foo); // "baz"

Это поведение изменилось в ES5, и литерал также создает новые объекты. Поведение также было исправлено во многих браузерах. окружение, поэтому на него нельзя положиться.

Если вы запустите этот пример во всех современных браузерах или NodeJS, вы получите следующее:

false
bar

То есть, когда вы вызываете функцию getRE(), новый объект RegExp создается даже при использовании буквального синтаксиса.

Вышеприведенное не только объясняет, почему вы не должны использовать RegExp() для неизменяемых регулярных выражений (это очень известная проблема производительности сегодня), но также объясняет:

(Я более удивлен тем, что inlineRegExp и StoreRegExp имеют разные Результаты.)

storedRegExp примерно на 5-20% быстрее во всех браузерах, чем inlineRegExp, потому что нет никаких затрат на создание (и сбор мусора) нового объекта RegExp каждый раз.

Вывод:
Всегда создавайте свои неизменяемые регулярные выражения с буквальным синтаксисом и кэшируйте их, если они будут использоваться повторно. Другими словами, не полагайтесь на эту разницу в поведении в envs ниже ES5 и продолжайте надлежащим образом кэшировать в envs выше.

Почему буквальный синтаксис? Он имеет некоторые преимущества по сравнению с синтаксисом конструктора:

  1. Это короче и не заставляет вас думать с точки зрения классового Конструкторы.
  2. При использовании конструктора RegExp() вам также необходимо экранировать кавычки и обратные косые черты с двойным экранированием. Делает регулярные выражения которые по своей природе трудно читать и понимать еще сложнее.

(Бесплатное цитирование из той же книги JavaScript "Шаблоны Стояна Стефанова").
Следовательно, всегда полезно придерживаться буквального синтаксиса, если ваше регулярное выражение не известно во время компиляции.

Ответ 2

Разница в производительности не связана с используемым синтаксисом частично связана с используемым синтаксисом: в /pattern/ и RegExp(/pattern/) (где вы не тестировали последний) регулярное выражение компилируется только один раз, но для RegExp('pattern') выражение компилируется при каждом использовании. Смотрите ответ александра, который должен быть принятым ответом сегодня.

Помимо вышесказанного, в ваших тестах для inlineRegExp и storedRegExp вы смотрите на код, который инициализируется один раз при разборе текста исходного кода, в то время как для dynamicRegExp регулярное выражение создается для каждого вызова метода., Обратите внимание, что фактические тесты запускают такие вещи, как r = dynamicRegExp(element), много раз, тогда как код подготовки запускается только один раз.

Ниже приведены примерно такие же результаты, согласно другому jsPerf:

var reContains = /(?:^| )foo(?: |$)/;

... и

var reContains = RegExp('(?:^| )foo(?: |$)'); 

... когда оба используются с

function storedRegExp(node) {
  return reContains.test(node.className);
}

Конечно, исходный код RegExp('(?:^| )foo(?: |$)') может сначала быть проанализирован в String, а затем в RegExp, но я сомневаюсь, что сам по себе он будет в два раза медленнее. Однако следующее будет снова и снова создавать новый RegExp(..) для каждого вызова метода:

function dynamicRegExp(node) {
  return RegExp('(?:^| )foo(?: |$)').test(node.className);
}

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

(Меня больше удивляет, что inlineRegExp и storedRegExp имеют разные результаты. Это также объясняется в ответе Александра.)

Ответ 3

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