更快的 JavaScript 修剪
由於 JavaScript 不包含 07
原生方法,它被無數的 JavaScript 庫包含——通常作為全局函數或附加到 10
.但是,我從未見過性能如此出色的實現,這可能是因為大多數程序員並沒有深入理解或關心正則表達式的效率問題。
在看到一個特別糟糕的22
之後 實施時,我決定做一些研究以找到最有效的方法。在進入分析之前,這裡是結果:
方法 | 火狐2 | IE 6 |
---|---|---|
trim1 | 15 毫秒 | <0.5ms |
trim2 | 31 毫秒 | <0.5ms |
trim3 | 46 毫秒 | 31 毫秒 |
trim4 | 47 毫秒 | 46 毫秒 |
trim5 | 156 毫秒 | 1656 毫秒 |
trim6 | 172 毫秒 | 2406 毫秒 |
trim7 | 172 毫秒 | 1640 毫秒 |
trim8 | 281 毫秒 | <0.5ms |
trim9 | 125 毫秒 | 78ms |
trim10 | <0.5ms | <0.5ms |
trim11 | <0.5ms | <0.5ms |
注 1: 比較基於修剪 Magna Carta (超過 27,600 個字符)在我的個人系統上帶有 20 次前導和尾隨空格。但是,您正在修剪的數據可能會對性能產生重大影響,詳情如下。
注2: 39
和 48
是當今 JavaScript 庫中最常見的。
注 3: 前面提到的糟糕的實現不包括在比較中,但稍後會顯示。
分析
儘管上表中有 11 行,但它們只是我編寫的大約 20 個版本中最值得注意的(出於各種原因),並針對各種類型的字符串進行了基準測試。以下分析是基於 Firefox 2.0.0.4 中的測試,雖然我已經註意到 IE6 的主要區別在哪裡。
56
綜合考慮,這可能是最好的全能方法。它的速度優勢在長字符串時最為顯著——當效率很重要時。速度很大程度上是由於 JavaScript 正則表達式解釋器內部的一些優化,這裡的兩個離散正則表達式觸發了這些優化。具體來說,所需字符的預檢查 和 字符串錨點的開始 優化,可能還有其他。63
非常類似於79
(上圖),但速度稍慢,因為它不會觸發所有相同的優化。83
這往往比下面的方法快,但比上面兩種慢。它的速度來自於使用簡單的字符索引查找。95
這種普遍想到的方法很容易成為當今 JavaScript 庫中最常用的方法。只有在處理不包含前導或尾隨空格的短字符串時,它通常是最快的實現。這種微小的優勢部分是由於首字母區分 它觸發的優化。雖然這是一個相對不錯的表現,但在處理較長的字符串時,它比上述三種方法要慢,因為頂級交替阻止了一些本來可以發揮作用的優化。108
這通常是處理空字符串或純空格字符串時最快的方法,因為需要預先檢查所需字符 它觸發的優化。注意:在 IE6 中,當處理較長的字符串時,這可能會很慢。115
這是一種相對常見的方法,部分由一些領先的 JavaScripters 推廣。它與128
的方法相似(但遜色) .沒有充分的理由在 JavaScript 中使用它,尤其是因為它在 IE6 中可能非常慢。135
同146
,但由於使用了非捕獲組(在 IE 5.0 及更低版本中不起作用),所以速度會更快一些。同樣,這在 IE6 中可能會很慢。151
這使用了一種簡單的、單程的、貪婪的方法。在 IE6 中,這太快了!性能差異表明 IE 在量化“任何字符”標記方面具有出色的優化能力。169
這通常是最快的,包含非空格字符和邊緣空白的非常短的字符串。這個小優勢是由於它使用了簡單的、單通道的、惰性的方法。喜歡178
,這在 IE6 中比 Firefox 2 快得多。
由於我在一個庫中看到了以下附加實現,因此我將在此處包含它作為警告:
return str.replace(/^\s*([\S\s]*)\b\s*$/, '$1');
儘管在處理包含非空格字符和邊緣空白的短字符串時,上述方法有時是最快的方法,但對於包含許多單詞邊界的長字符串,它的性能非常差,而且長字符串由任何內容組成,這很糟糕(!)空白,因為這會觸發呈指數增長的回溯量。請勿使用。
不同的結局
本文頂部的表格中有兩種方法尚未涵蓋。對於這些,我使用了非正則表達式和混合方法。
在比較和分析了以上所有內容之後,我想知道不使用正則表達式的實現會如何執行。這是我嘗試過的:
function trim10 (str) { var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000'; for (var i = 0; i < str.length; i++) { if (whitespace.indexOf(str.charAt(i)) === -1) { str = str.substring(i); break; } } for (i = str.length - 1; i >= 0; i--) { if (whitespace.indexOf(str.charAt(i)) === -1) { str = str.substring(0, i + 1); break; } } return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''; }
那表現如何?好吧,對於不包含過多前導或尾隨空格的長字符串,它會擊敗競爭對手(除了 187
/190
/200
在 IE 中,已經非常快了)。
這是否意味著正則表達式在 Firefox 中很慢?一點都不。這裡的問題是,雖然正則表達式非常適合修剪前導空白,除了 .NET 庫(它提供了一種有點神秘的“向後匹配”模式)之外,它們並沒有真正提供跳轉到末尾的方法一個字符串,甚至不考慮前面的字符。但是,不依賴正則表達式的 214
函數就是這樣做的,第二個循環從字符串的末尾向後工作,直到找到一個非空白字符。
知道了這一點,如果我們創建一個混合實現,將正則表達式在修剪前導空白方面的通用效率與替代方法在刪除尾隨字符方面的速度相結合會怎樣?
function trim11 (str) { str = str.replace(/^\s+/, ''); for (var i = str.length - 1; i >= 0; i--) { if (/\S/.test(str.charAt(i))) { str = str.substring(0, i + 1); break; } } return str; }
雖然上面比 228
慢了一點 使用一些字符串,它使用的代碼要少得多,而且速度仍然很快。另外,對於包含大量前導空格的字符串(包括僅由空格組成的字符串),它比 233
快得多 .
總之……
由於跨瀏覽器實現之間的差異以及與不同數據一起使用時的差異既複雜又微妙(沒有一個比其他所有數據都快),這是我對 的一般建議244代碼> 方法:
- 使用
254
如果您想要一個快速跨瀏覽器的通用實現。 - 使用
266
如果您想在所有瀏覽器中快速處理長字符串。
要自己測試所有上述實現,請嘗試我的非常基本的基準測試頁面。後台處理可能會導致結果嚴重偏斜,因此多次運行測試(無論您指定多少次迭代)並只考慮最快的結果(因為平均背景干擾的成本並不是很有啟發性)。主頁>
最後一點,雖然有些人喜歡緩存正則表達式(例如使用全局變量),以便它們可以重複使用而無需重新編譯,但 IMO 這對於 276
沒有多大意義 方法。上述所有正則表達式都非常簡單,編譯時間通常不超過一納秒。此外,一些瀏覽器會自動緩存最近使用的正則表達式,因此使用 289
的典型循環 並且不包含一堆其他正則表達式可能無論如何都不會遇到重新編譯。
編輯(2008-02-04): 發布後不久,我意識到 293
/304
可以寫得更好。一些人還在評論中發布了改進的版本。這是我現在使用的,它採用 316
-style 混合方法:
function trim12 (str) { var str = str.replace(/^\s\s*/, ''), ws = /\s/, i = str.length; while (ws.test(str.charAt(--i))); return str.slice(0, i + 1); }
新庫: 您是 JavaScript 正則表達式大師,還是想成為?那你需要我喜歡的 XRegExp 庫 .它添加了新的正則表達式語法(包括命名捕獲和 Unicode 屬性); 327
, 336
, 和 342
旗幟;強大的正則表達式工具;它修復了討厭的瀏覽器不一致。 看看吧!