2012-03-17 78 views
25

內嵌正則表達式的表現我偶然發現的性能測試,他說,在JavaScript正則表達式不一定慢:http://jsperf.com/regexp-indexof-perf動態VS在JavaScript

有一件事我沒有得到,雖然二例涉及的東西,我認爲完全一樣:

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

而且

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

在我的腦海裏,這兩條線分別爲究竟一樣,第二個是某種速記創建一個RegExp對象。仍然是比第一次快

這些情況被稱爲「動態正則表達式」和「內聯正則表達式」。

有人能幫我理解這兩者之間的區別(和性能差距)嗎?

+1

「內聯」版本更快,因爲它比使用顯式構造函數要難得多。 – Pointy 2012-03-17 13:32:19

+0

其中之一,你可能已經覆蓋'RegExp',所以它必須查找函數,而不是直接評估它,和b)第二個可以在解析時評估,而第一個不能因爲調用'RegExp'可以有如果你已經覆蓋它的副作用。 – pimvdb 2012-03-17 13:36:03

回答

8

性能的差異與所使用的語法無關。

對於inlineRegExpstoredRegExp您正在查看源代碼文本解析時初始化的代碼,而對於dynamicRegExp正則表達式是爲每次調用該方法創建的。請注意,actual tests多次運行諸如r = dynamicRegExp(element)之類的東西,而準備代碼僅運行一次。

下面爲您提供有關相同的結果,according to another 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時間更快。

(我更驚訝的是,inlineRegExpstoredRegExp有不同的結果。)

+1

這實際上很有意義,謝謝。您可能想突出顯示[您的jsperf](http://jsperf.com/regexp-indexof-perf/24),因爲它幫助我瞭解性能差異來自哪裏(dynamicStoredRegExp和dynamicRegExp之間)。關於你最後的陳述,這沒什麼區別,我會解僱這個。 – Pioul 2012-11-05 16:28:58

+0

至於'inlineRegExp'和'storedRegExp'之間的區別:第一個[在最新的Safari 6.0中快了一半](http://jsperf.com/regexp-indexof-perf/24)...‽ – Arjan 2012-11-05 20:49:38

+1

沒有看到,在Safari 6.0上,'storedRegExp'和'dynamicStoredRegExp'的速度是'inlineRegExp'的兩倍,而在其他瀏覽器上它幾乎相同。現在,我也很好奇Safari可能會發生什麼...... – Pioul 2012-11-06 16:05:04

6

在第二種情況下,正則表達式對象是在解析語言期間創建的,在第一種情況下,RegExp類構造函數必須解析任意字符串。

+0

你說的是,在第一種情況下,正則表達式在被引擎「理解」之前會經歷一個「字符串」狀態? – Pioul 2012-03-17 13:47:54

+0

是的,沒錯。 – dldnh 2012-03-17 13:49:08

+0

哦,甚至更好,告訴我,如果我是正確的:斜線工作正則表達式分隔符,以便斜線暗示一個正則表達式,儘可能多的引用暗示一個字符串? – Pioul 2012-03-17 13:49:28

13

如今,這裏給出的答案並不完全完整的/正確的。

從ES5開始,文字語法的行爲是一樣的RegExp()語法關於創建對象:他們兩人創建了一個新的RegExp對象每次代碼路徑擊中它們所參與的表達式。

因此,他們之間的唯一區別,現在是正則表達式多久編譯

  • 用文字語法 - 一次初始代碼解析期間和 編譯
  • 隨着RegExp()語法 - 每次創建新對象

見,例如,Stoyan Stefanov's JavaScript Patterns書:

正則表達式文字和 構造的另一個區別是,字面期間 分析時創建一個對象只有一次。如果您在循環中創建相同的正則表達式,則會返回之前創建的對象,其所有屬性 (如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和storedRegExp有不同 結果)

storedRegExp是約5 - 跨瀏覽器比inlineRegExp快20%百分比,因爲沒有創建(和垃圾收集)的新對象RegExp前夕的開銷時間。

結論:
始終創建與文字語法和緩存它一成不變的正則表達式,如果它被重新使用。換句話說,不要依賴ES5之下的envs行爲的差異,並在上面的envs中繼續緩存。

爲什麼字面語法?它具有一定的優勢比較構造函數的語法:

  1. 它是短,不強迫你覺得在課堂上樣 構造方面。
  2. 使用RegExp()構造函數時,還需要轉義引號和雙重轉義反斜槓。它使正則表達式 難以閱讀和理解其性質更難。

(引自同一Stoyan Stefanov's JavaScript Patterns免費書)。
因此,堅持使用字面語法總是一個好主意,除非編譯時不知道正則表達式。

+0

感謝您的更新!我也喜歡這個結論,儘管我會試着說「用你喜歡的任何東西來創建一個正則表達式,文字或構造函數,並且如果它被重用,就將其緩存起來」。換句話說,不要依賴ES5下面的envs行爲的差異,並在上面的envs中繼續緩存:) – Pioul 2015-09-12 14:53:28

+1

@Pioul,感謝您的反饋!我已經更新了我的答案,並按照您的建議添加了,除了關於構造函數模式的部分。看到我的答案爲什麼:) – 2015-09-14 12:34:41

+0

阿門! :d – Pioul 2015-09-14 23:34:54