Close Advertisement
如果你看到别人写trim函数是用循环而不用正则表达式来写,请不要取笑,也许,他们就是高手。如果你很自信你的trim函数效率很高,请看完本文再下结论。
一般情况下用正则写法为:
<script type="text/javascript">//<![CDATA[
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]+$/g, '');
}
s = ' never-online\'s weblog, www.never-online.net/blog ';
alert(s.trim().length);
//]]></script>
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]+$/g, '');
}
s = ' never-online\'s weblog, www.never-online.net/blog ';
alert(s.trim().length);
//]]></script>
如果遇到大数据的变长字符串的话就会发现这个是很耗资源的。效率并不高,有的时候甚至无法忍受。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="ImageToolbar" content="no" />
<style type="text/css" title="default" media="screen">
/*<![CDATA[*/
/*]]>*/
</style>
</head>
<body>
<textarea>请在这里写足够多的空格或者tab字符。</textarea>
<script type="text/javascript">//<![CDATA[
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]+$/g, '');
}
var s = document.getElementsByTagName('textarea')[0].value
var d = new Date();
s.trim();
alert(new Date()-d);
//]]></script>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="ImageToolbar" content="no" />
<style type="text/css" title="default" media="screen">
/*<![CDATA[*/
/*]]>*/
</style>
</head>
<body>
<textarea>请在这里写足够多的空格或者tab字符。</textarea>
<script type="text/javascript">//<![CDATA[
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]+$/g, '');
}
var s = document.getElementsByTagName('textarea')[0].value
var d = new Date();
s.trim();
alert(new Date()-d);
//]]></script>
</body>
</html>
在解释这个原因的时候想起以前看到master regular expression里面有提到过。NFA和DFA的引擎是有区别的。js/perl/php/java/.net都是NFA引擎。
而DFA与NFA机制上的不同带来5个影响:
1. DFA对于文本串里的每一个字符只需扫描一次,比较快,但特性较少;NFA要翻来覆去吃字符、吐字符,速度慢,但是特性丰富,所以反而应用广泛,当今主要的正则表达式引擎,如Perl、Ruby、Python的re模块、Java和.NET的regex库,都是NFA的。
2. 只有NFA才支持lazy和backreference(后向引用)等特性;
3. NFA急于邀功请赏,所以最左子正则式优先匹配成功,因此偶尔会错过最佳匹配结果;DFA则是“最长的左子正则式优先匹配成功”。
4. NFA缺省采用greedy量词(就是对于/.*/、/\w+/这样的“重复n”次的模式,以贪婪方式进行,尽可能匹配更多字符,直到不得以罢手为止),NFA会优先匹配量词。
5. NFA可能会陷入递归调用的陷阱而表现得性能极差。
backtracking(回朔)
当NFA发现自己吃多了,一个一个往回吐,边吐边找匹配,这个过程叫做backtracking。由于存在这个过程,在NFA匹配过程中,特别是在编写不合理的正则式匹配过程中,文本被反复扫描,效率损失是不小的。明白这个道理,对于写出高效的正则表达式很有帮助。
定位/分析原因
在解释上面的trim原型方法的时候。经过测试,先不说结果是否正确,有几个方法是可以化解JS NFA引擎的回朔次数的
a. 去掉限定的量词,即改成
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]$/g, '');
}
b. 去掉字符串尾匹配。即改成:return this.replace(/^[\s\t ]+|[\s\t ]$/g, '');
}
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+/g, '');
}
c.加入多行匹配。即改成:return this.replace(/^[\s\t ]+/g, '');
}
String.prototype.trim = function () {
return this.replace(/^[\s\t ]+|[\s\t ]+$/mg, '');
}
return this.replace(/^[\s\t ]+|[\s\t ]+$/mg, '');
}
从以上三种改法结合文中开头的NFA资料,我们可以大概的知道trim性能出现问题的原因
- 量词限定将优先匹配。
- 量词限定在结尾可能会使JS的正则引擎不停的回朔,出现递归的一个陷阱,这个递归的深度太深。如果字符串更大一点应该会出现栈溢出了。
- 多行既然能够匹配,而且性能消耗不大。性能上没有任何问题,从一个写这个正则程序的人角度上去看,多行明显比单行要替换的空串多得多。所以第二点的结论应该是对的
首先确定匹配字符串的开始正则是没有任何效率问题的。而匹配结束的时候会出现性能问题,那可以采用正则与传统相结合来改善这个trim性能问题。
例如:
<script type="text/javascript">//<![CDATA[
String.prototype.trim = function () {
var s = this.replace(/^[\s\t ]+/g, '');
从s后端开始查找,并回循环到最后一个非空字符串,代码略。
}
//]]></script>
String.prototype.trim = function () {
var s = this.replace(/^[\s\t ]+/g, '');
从s后端开始查找,并回循环到最后一个非空字符串,代码略。
}
//]]></script>
see also:firefox 2 的正则表达式细节
[最后修改由 Rank, 于 2008-12-09 00:13:43]
评论Feed: http://www.never-online.net/blog/feed.asp?q=comment&id=259


不过有没有效率对比的数据呢?前后都使用循环或其他方法,效率比之如何呢?
至于默认是悄悄话是因为现在国内的形势(gov)是要把发言的内容都要进行审核。。。。被逼无奈而为之~~~
比头部使用正则(replace),尾部使用循环(然后substring)的方式,
效率还高3倍左右(大文本,不同型号的比率大概也不同),
应该是replace效率较低的缘故。
如果能实现快速找到第一个非空白和最后一个非空白的位置,效率就更高了,可惜我的智慧还达不到。
p.s. 貌似你的安全规则是hard code..
第一个非空白和最后一个非空白的位置,这个用正则应该还是可以做到的,用预查应该可以做到,且不消耗存储。
var s = ' s ';
/(?=[^\s])/.test(s);
alert(RegExp.lastIndex);
//]]></script>