JavaScript去除字符串首尾空白

去除字符串首尾空白是个简单常见的任务,ES5 已经添加了原生的 trim() 方法,我们之所以还要探究其实现,是因为其作为学习优化正则表达式的例子再好不过了。

使用正则表达式的解决方案

最常见解决方案

如果使用正则表达式解决这个问题,我们或许立刻会想到以下解决方案:

1
2
3
4
5
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
}
}

这个方法的不足之处在于,若目标为较长字符串,那么正则表达式中的条件分支功能会拖慢速度。

两次替换方案

如果不使用具有条件分支的正则表达式,我们可以写出如下实现:

1
2
3
4
5
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
}
}

这种方式通过对目标字符串执行两次替换操作从而避免了条件分支的性能开销,与最常见的解决方案相比,此种方案在处理长字符串时速度会得到显著提升。

称不上优化的优化

我们还可以将两次替换方案中所用正则表达式的 \s+ 改为 \s\s* 来达到额外优化的效果,尽管这并没有什么明显的性能提升。

1
2
3
4
5
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
}

不使用正则表达式的解决方案

循环遍历字符串确定空白字符

如果不使用正则表达式,那么我们就不可避免地需要使用循环遍历字符串来找出空白字符,然后使用相应的字符串原生方法实现需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (!String.prototype.trim) {
String.prototype.trim = function() {
const ws = '\n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\202f\205f\u3000\ufeff';
let start = 0;
let end = this.length - 1;
while (ws.indexOf(this.charAt(start)) > -1) {
++start;
}
while (end > start && ws.indexOf(this.charAt(end) > -1)) {
--end;
}
return this.slice(start, end + 1);
}
}

上述代码中的 ws 变量包含了 ECMAScript 中定义的所有空白字符。出于性能考虑,在得到修剪后的起始和终止的位置之前避免拷贝字符串的任何部分。

尽管这个版本的解决方案其性能不受字符串的总长度影响,但它在处理前后有大量空白的字符时却显得费力,这是因为通过循环遍历字符串来确定空白字符串到的效率不够高。

混合解决方案

可以通过混合使用正则表达式解决方案与 slice() 方法来使性能更高效,具体来说,即使用正则表达式解决方案过滤字符串头部空白,使用非正则表达式解决方案过滤字符串尾部空白。

1
2
3
4
5
6
7
8
9
10
11
if (!String.prototype.trim) {
String.prototype.trim = function() {
const ws = /\s/;
let str = this.replace(/^\s\s*/, '');
let end = str.length;
while (ws.test(str.charAt(--end))) {}
return str.slice(0, end + 1);
}
}

这种混合解决方案尤其在处理更长的字符串时性能显著,超越了之前的解决方案,但在处理短字符串时耗时还会多于使用正则表达式的解决方案。