JavaScript字符串是不可变的吗?我需要JavaScript中的"字符串构建器"吗?

Dev*_*ris 223 javascript string

javascript使用不可变或可变的字符串吗?我需要一个"字符串构建器"吗?

Jua*_*des 281

他们是不变的.您无法使用类似的内容更改字符串中的字符var myString = "abbdef"; myString[2] = 'c'.该字符串操作方法,例如trim,slice返回新的字符串.

同样,如果您对同一个字符串有两个引用,则修改一个字符串不会影响另一个字符串

let a = b = "hello";
a = a + " world";
// b is not affected
Run Code Online (Sandbox Code Playgroud)

但是,我总是听到Ash在他的回答中提到的内容(使用Array.join更快地进行连接)所以我想测试连接字符串的不同方法并将最快的方法抽象到StringBuilder中.我写了一些测试,看看这是否属实(不是!).

这是我认为最快的方式,但我一直认为添加一个方法调用可能会让它变慢...

function StringBuilder() {
    this._array = [];
    this._index = 0;
}

StringBuilder.prototype.append = function (str) {
    this._array[this._index] = str;
    this._index++;
}

StringBuilder.prototype.toString = function () {
    return this._array.join('');
}
Run Code Online (Sandbox Code Playgroud)

这是性能速度测试.他们三个都创造了一个巨大的字符串,由"Hello diggity dog"一串空串连接十万次组成.

我创建了三种类型的测试

  • 使用Array.pushArray.join
  • 使用Array索引来避免Array.push,然后使用Array.join
  • 直串连接

然后我通过抽象它们创建了相同的三个测试StringBuilderConcat,StringBuilderArrayPush并且StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5请去那里运行测试,这样我们就可以得到一个很好的样本.请注意,我修复了一个小错误,因此测试数据被擦除,一旦有足够的性能数据,我将更新表.转到旧数据表的http://jsperf.com/string-concat-without-sringbuilder/5.

如果您不想关注该链接,请参阅2013年2月21日的部分数据.每次测试的数量都在运行/秒(越高越好)

| Browser          | Index | Push | Concat | SBIndex | SBPush | SBConcat |
---------------------------------------------------------------------------
| Chrome 71.0.3578 | 988   | 1006 | 2902   | 963     | 1008   | 2902     |
| Firefox 65       | 1979  | 1902 | 2197   | 1917    | 1873   | 1953     |
| Edge             | 593   | 373  | 952    | 361     | 415    | 444      |
| Exploder 11      | 655   | 532  | 761    | 537     | 567    | 387      |
| Opera 58.0.3135  | 1135  | 1200 | 4357   | 1137    | 1188   | 4294     | 
Run Code Online (Sandbox Code Playgroud)

发现

  • 如今,所有浏览器都能很好地处理字符串连接.Array.join只能帮助Opera

  • 总的来说,Chrome是最快的,在27.0中以1025 ops/sec为单位.比使用Array.join()快10倍

  • Firefox排在第二位,大约550次操作/秒(但20.0似乎已经退步).Array.join大约慢了4-5倍.

  • IE是最快的直接串连接,使用var myString = "abbdef"; myString[2] = 'c'和它真的很慢trim.IE 9 slice不会那么慢,并且所有SB抽象的执行方式几乎相同(可能是因为方法开销)

  • Opera是"Hello diggity dog"实际帮助的唯一一个,它的速度是字符串连接的2-3倍.

  • 创建一个StringBuilder来抽象出每个浏览器的性能问题弊大于利.方法调用的开销可能是可以接受的,但趋势似乎是浏览器更聪明地处理字符串连接.只有你的目标受众是Opera才有意义,所以你可以在那里使用Array.join并在其他地方使用String连接(这意味着所有其他浏览器都受到了攻击)

希望别人觉得这很有用

不同的测试用例

由于@RoyTinker认为我的测试存在缺陷,因此我创建了一个新的案例,它不会通过连接相同的字符串来创建大字符串,它会为每次迭代使用不同的字符.字符串连接似乎仍然更快或更快.让我们让这些测试运行起来.

我建议每个人都应该考虑其他方法来测试这个,并随意添加新链接到下面的不同测试用例.

http://jsperf.com/string-concat-without-sringbuilder/7

  • @RoyTinker是的,任何字符串构建器都需要构建数组.问题是关于是否需要字符串构建器.如果你已经有一个数组中的字符串,那么它不是我们在这里讨论的有效测试用例 (2认同)

min*_*nty 42

来自犀牛书:

在JavaScript中,字符串是不可变对象,这意味着它们中的字符可能不会被更改,并且对字符串的任何操作实际上都会创建新字符串.字符串按引用分配,而不是按值分配.通常,当通过引用分配对象时,通过一个引用对对象所做的更改将通过对该对象的所有其他引用可见.但是,由于无法更改字符串,因此您可以对字符串对象进行多次引用,而不必担心字符串值会在您不知情的情况下发生更改

  • Rhino书的引用(以及这个答案)在这里是_wrong_.在JavaScript中,字符串是原始值类型和_not_ objects [(spec)](http://es5.github.io/#x4.3.16).事实上,从ES5开始,它们是"null"`undefined``number`和`boolean`的唯一5种值类型之一.字符串由_value_和_not_按引用分配,并按原样传递.因此,字符串不仅是不可变的,它们是_value_.将字符串"hello"更改为"世界"`就像决定从现在开始,数字3是数字4 ......这没有任何意义. (113认同)
  • @VidarS.Ramdal不,使用字符串构造函数创建的`String`对象是围绕JavaScript字符串值的_wrappers_.您可以使用`.valueOf()`函数访问盒装类型的字符串值 - 对于`Number`对象和数值也是如此.重要的是要注意使用`new String`创建的`String`对象不是实际的字符串,而是围绕字符串的_wrappers_或_boxes_.见http://es5.github.io/#x15.5.2.1.关于事物如何转换为对象,请参阅http://es5.github.io/#x9.9 (9认同)
  • 是的,就像我的评论说字符串_are_不可变,但它们不是引用类型,也不是对象 - 它们是原始值类型.一种简单的方法是看到它们既不会尝试向字符串添加属性然后读取它:`var a ="hello"; var b = a; ax = 5; console.log(ax,bx) ;` (8认同)
  • 链接到犀牛书的相应部分:http://books.google.com/books?id = VOS6IlCsuU4C&p = PA47&vq = string&immutable&dq = javascript&client = firefox-a&source = gbs_search_s&cad = 0 (7认同)
  • 至于为什么有些人说字符串是对象,它们可能来自Python或Lisp或任何其他语言,其规范使用"对象"一词来表示任何类型的数据(甚至是整数).他们只需要阅读ECMA规范如何定义单词:"Object类型的成员".而且,根据不同语言的规格,即使"价值"这个词也可能意味着不同的东西. (5认同)
  • @BenjaminGruenbaum所以,可以说JS字符串*是*不可变的,但它是*因为*它们是原始而不是对象? (2认同)
  • @BenjaminGruenbaum - 对于用`new String("...")`创建的字符串也是如此?我发现[this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Distinction_between_string_primitives_and_String_objects)很有意思:"请注意,JavaScript区分String对象和原始字符串值.(布尔和数字也是如此.)" (2认同)

Ash*_*Ash 20

表现提示:

如果必须连接大字符串,请将字符串部分放入数组中并使用该Array.Join()方法获取整个字符串.对于连接大量字符串,这可以快许多倍.

StringBuilderJavaScript中没有.

  • 根据Juan的上述测试,字符串连接在IE和Chrome中实际上更快,而在Firefox中更慢. (8认同)
  • 考虑更新你的答案,很久以前可能是真的,但现在已经不是了.请参阅http://jsperf.com/string-concat-without-sringbuilder/5 (8认同)
  • @docgnome:因为字符串是不可变的,所以字符串连接需要创建比Array.join方法更多的对象 (4认同)
  • 这与字符串是不可变的有什么关系? (3认同)

Kat*_*ink 15

只是为了澄清像我这样的简单头脑(来自MDN):

不可变对象是一旦创建对象其状态就无法更改的对象。

字符串和数字是不可变的。

不可变意味着:

您可以使变量名称指向一个新值,但先前的值仍保留在内存中。因此需要垃圾收集。

var immutableString = "Hello";

// 在上面的代码中,创建了一个带有字符串值的新对象。

immutableString = immutableString + "World";

// 我们现在将“World”附加到现有值。

这看起来我们正在改变字符串 'immutableString',但我们没有。反而:

将字符串值附加到“immutableString”后,会发生以下事件:

  1. 检索“immutableString”的现有值
  2. “World”附加到“immutableString”的现有值
  3. 然后将结果值分配给新的内存块
  4. “immutableString”对象现在指向新创建的内存空间
  5. 以前创建的内存空间现在可用于垃圾收集。

  • 您将用新的内存字段替换它。旧字符串被丢弃。 (3认同)

小智 5

字符串类型值是不可变的,使用 String() 构造函数创建的 String 对象是可变的,因为它是一个对象,您可以向它添加新属性。

> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }
Run Code Online (Sandbox Code Playgroud)

同时,虽然您可以添加新属性,但无法更改已经存在的属性

Chrome 控制台中的测试屏幕截图

综上所述,1.所有字符串类型值(原始类型)都是不可变的。2. String对象是可变的,但它包含的字符串类型值(原始类型)是不可变的。

  • `new String` 围绕不可变字符串生成可变包装器 (7认同)
  • 通过运行上面@zhanziyang的代码很容易测试。您完全可以向“String”对象(包装器)添加新属性,这意味着它不是不可变的(默认情况下;像任何其他对象一样,您可以对其调用“Object.freeze”以使其不可变)。但是原始字符串值类型,无论是否包含在“String”对象包装器中,始终是不可变的。 (2认同)