本文深入探讨 JavaScript 中字符串连接的底层机制与性能差异。由于 ECMAScript 字符串不可变,频繁使用 + 运算符会导致大量内存分配与复制开销。文章通过对比传统加号连接、Array.join()、自定义 StringBuffer 类以及 String.concat() 方法的执行耗时,验证了封装 StringBuffer 模式在处理大量字符串拼接时的显著性能优势,并提供具体的测试代码与结果分析。
JavaScript 字符串不可变性与性能瓶颈
JavaScript 的核心规范为 ECMAScript。与多数编程语言类似,ECMAScript 中的字符串具有不可变性,这意味着一旦字符串被创建,其内容便无法被修改。
为了直观理解这一特性,我们可以观察以下代码示例:
代码如下:
var str = "hello ";
str += "world"; 实际上,这段看似简单的代码在底层执行时,会经历一系列复杂的步骤:1. 创建存储 "hello " 的字符串对象。
2. 创建存储 "world" 的字符串对象。
3. 分配一块新的内存空间,用于存储连接后的结果字符串。
4. 将 str 当前的内容复制到新分配的内存空间中。
5. 将 "world" 的内容追加复制到结果字符串中。
6. 更新变量 str 的引用,使其指向新创建的字符串对象。
基于 Array 与 StringBuffer 的优化方案
每次执行字符串连接操作时,上述步骤 2 至 6 都会重复执行,这导致该操作非常消耗系统资源。如果在循环中重复此过程数百次甚至数千次,将会引发严重的性能瓶颈。为了解决这一问题,推荐使用 Array 对象来暂存字符串,最后通过 join() 方法(传入空字符串作为分隔符)一次性生成最终字符串。例如,可以使用以下代码替代前述操作:
代码如下:
var arr = new Array();
arr[0] = "hello ";
arr[1] = "world";
var str = arr.join("");采用这种方式,无论数组中累积了多少字符串,连接操作仅在调用 join() 方法时发生一次。此时,底层执行的步骤简化为:
1. 创建存储最终结果的字符串。 2. 将数组中的每个字符串按顺序复制到结果字符串的对应位置。 虽然这种方案效果良好,但代码的可读性与意图表达仍有提升空间。为了更清晰地封装这一功能,我们可以定义一个 StringBuffer 类:
代码如下:
function StringBuffer () {
this._strings_ = new Array();
}
StringBuffer.prototype.append = function(str) {
this._strings_.push(str);
};
StringBuffer.prototype.toString = function() {
return this._strings_.join("");
};观察上述代码,首先注意到 _strings_ 属性旨在作为私有属性使用。该类仅包含两个方法:append() 和 toString()。append() 方法接收一个参数,将其推入内部字符串数组;toString() 方法则调用数组的 join 方法,返回真正连接好的字符串。若要使用 StringBuffer 对象连接一组字符串,可参考以下代码:
代码如下:
var buffer = new StringBuffer ();
buffer.append("hello ");
buffer.append("world");
var result = buffer.toString();性能测试对比:加号、StringBuffer 与 concat
基于上述实现,我们进行运行时间对比,以评估使用“+”逐个连接字符串与我们封装的工具类之间的性能差异。可以使用以下代码在 Chrome 控制台运行测试,以比较 StringBuffer 对象与传统字符串连接方法的性能:
代码如下:
var d1 = new Date();
var str = "";
for (var i=0; i < 10000; i++) {
str += "text";
}
var d2 = new Date();
console.log("Concatenation with plus: "
+ (d2.getTime() - d1.getTime()) + " milliseconds");
var buffer = new StringBuffer();
d1 = new Date();
for (var i=0; i < 10000; i++) {
buffer.append("text");
}
var result = buffer.toString();
d2 = new Date();
console.log("Concatenation with StringBuffer: "
+ (d2.getTime() - d1.getTime()) + " milliseconds");这段代码执行两项测试:第一项使用加号运算符,第二项使用 StringBuffer 类。每个操作均连接 10000 个字符串。变量 d1 和 d2 用于记录操作开始和结束的时间戳,从而计算耗时。请注意,创建 Date 对象时若未传参,其值为当前日期和时间。通过计算两个日期对象毫秒表示的差值(使用 getTime() 方法),即可得出连接操作所耗费的时间。这是衡量 JavaScript 性能的常用方法,测试结果有助于直观比较使用 StringBuffer 类与使用加号的效率差异。
上例运行结果如下:

或许有人会质疑,JavaScript 的 String 对象本身已封装了 concat() 方法,我们不妨也用 concat() 方法执行同样的操作。请在控制台输入以下代码进行测试:
代码如下:
var d1 = new Date();
var str = "";
for (var i=0; i < 10000; i++) {
str.concat("text");
}
var d2 = new Date();
console.log("Concatenation with plus: "
+ (d2.getTime() - d1.getTime()) + " milliseconds");执行 10000 次字符串连接后,我们可以观察到其耗时情况:

由此可以得出结论,当涉及大量字符串连接操作时,在 JavaScript 中封装类似 Java 中 StringBuffer 的类(或函数)进行字符串拼接,能够在性能上获得显著提升。