JavaScript字符串是不可变的吗?我需要JavaScript中的“字符串生成器”吗?


Answers:


294

他们是一成不变的。您不能使用类似的字符来更改字符串中的字符var myString = "abbdef"; myString[2] = 'c'。该字符串操作方法,例如trimslice返回新的字符串。

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

let a = b = "hello";
a = a + " world";
// b is not affected

但是,我总是听到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('');
}

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

我创建了三种类型的测试

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

然后,我通过将它们抽象为StringBuilderConcatStringBuilderArrayPushStringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5来创建了这三个测试,请去那里运行测试,以便获得一个不错的示例。请注意,我修复了一个小错误,因此擦除了测试数据,一旦有足够的性能数据,我将更新表。请访问http://jsperf.com/string-concat-without-sringbuilder/5以获取旧数据表。

如果您不想点击链接,请查看以下数字(Ma5rch 2018中的最新更新)。每次测试的数量以1000次操作/秒为单位(越高越好

| 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     | 

发现

  • 如今,所有常绿的浏览器都能很好地处理字符串连接。Array.join仅帮助IE 11

  • 总体而言,Opera最快,是Array.join的4倍

  • Firefox位居第二,Array.joinFF的速度稍慢,但Chrome的速度却慢得多(3倍)。

  • Chrome排名第三,但字符串concat的速度比Array.join快3倍

  • 创建一个StringBuilder似乎不会对性能产生太大影响。

希望其他人觉得这有用

不同的测试用例

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

我建议每个人都应继续思考其他测试方法,并随时在下面的不同测试用例中添加新链接。

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


@Juan,您要求我们访问的链接将30个字符组成的字符串连接起来。这是另一个可能有助于平衡的测试-Array.join与20,000个不同的 1个字符的字符串的字符串连接(IE / FF上的连接要快得多)。 jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@RoyTinker Roy,哦Roy,您的测试正在作弊,因为您正在测试的设置中创建阵列。这是使用不同字符的真实测试jsperf.com/string-concat-without-sringbuilder/7可以随意创建新的测试用例,但是创建数组是测试本身的一部分
Juan Mendes 2013年

@JuanMendes我的目标是将测试用例缩小为joinvs字符串连接的严格比较,从而在测试之前构建数组。我不认为这是可以理解的目标(并join在内部枚举数组,因此也可以forjoin测试中省略一个循环)。
罗伊·廷克

2
@RoyTinker是的,任何字符串构建器都需要构建数组。问题是关于是否需要字符串构建器。如果您已经在数组中包含字符串,那么对于我们在此处讨论的内容而言,这不是有效的测试用例
Juan Mendes

1
@Baltusaj说索引/推送的列正在使用Array.join
Juan Mendes

41

犀牛书中

在JavaScript中,字符串是不可变的对象,这意味着其中的字符可能不会更改,并且对字符串的任何操作实际上都会创建新的字符串。字符串是通过引用而不是值来分配的。通常,当通过引用分配对象时,通过一个引用对对象所做的更改将通过对该对象的所有其他引用可见。但是,由于不能更改字符串,因此可以有多个对字符串对象的引用,不必担心字符串值会在不知不觉中发生变化。


7
链接到犀牛书的相应章节:books.google.com/...
baudtack

116
Rhino的书名(以及此答案)在这里是错误的。在JavaScript中,字符串是原始值类型,而不是对象(规范)。实际上,从ES5开始,它们是null undefined number和旁边仅有的5种值类型之一boolean。字符串是通过不是通过引用分配的,并按原样传递。因此,字符串不仅是不可变的,而且是一个。更改字符串"hello""world"就像决定,从现在起的3号是多少4 ...这没有任何意义。
本杰明·格伦鲍姆

8
是的,就像我的评论所说的那样,字符串不可变的,但是它们不是引用类型,也不是对象,它们是原始值类型。看到它们都不是的一种简单方法是尝试向字符串中添加一个属性,然后读取它:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum 2013年

9
@ VidarS.Ramdal不,String使用字符串构造函数创建的对象是JavaScript字符串值的包装。您可以使用.valueOf()函数访问盒装类型的字符串值-对于Number对象和数字值也是如此。重要的是要注意String使用创建的对象new String不是实际的字符串,而是字符串的包装。见es5.github.io/#x15.5.2.1。有关事物如何转换为对象的信息,请参见es5.github.io/#x9.9
Benjamin Gruenbaum

5
至于为什么有人说字符串是对象,它们可能来自Python或Lisp或任何其他语言,其规范使用“对象”一词来表示任何种类的数据(甚至是整数)。他们只需要阅读ECMA规范如何定义单词:“ Object类型的成员”。同样,根据不同语言的规范,即使是“值”一词也可能意味着不同的含义。
Jisang Yoo 2014年

21

性能提示:

如果必须连接较大的字符串,请将字符串部分放入数组中,然后使用该Array.Join()方法获取整个字符串。连接大量字符串的速度可能快许多倍。

StringBuilderJavaScript中没有。


我知道没有一个StringBuilder,msAjax有一个,我刚琢磨是否其可用
DevelopingChris

5
这与字符串不可变有什么关系?
baudtack

4
@docgnome:因为字符串是不可变的,所以字符串连接需要创建比Array.join方法更多的对象
Juan Mendes

8
根据上述Juan的测试,字符串连接实际上在IE和Chrome中都更快,而在Firefox中则更慢。
比尔·杨

9
考虑更新您的答案,这可能很久以前是正确的,但现在已经不复存在了。参见jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes

8

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

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

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

不可变意味着:

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

var immutableString = "Hello";

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

immutableString = immutableString + "World";

//现在,我们将“世界”附加到现有值上。

看起来我们正在对字符串'immutableString'进行突变,但事实并非如此。代替:

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

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

4

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

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

同时,尽管可以添加新属性,但是不能更改已经存在的属性

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

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



@prasun,但在该页面中显示:“除对象以外的所有类型都定义了不可变的值(不能更改的值)。”字符串对象是对象。如果可以在其上添加新属性,它怎么不可变?
zhanziyang

阅读“字符串类型”部分。Javascript的String链接指向原始和Object,后来又说“ JavaScript字符串是不可变的”。似乎该文档在该主题上不清楚,因为它在两个不同的注释上发生了冲突
prasun 2015年

6
new String生成围绕不变字符串的可变包装器
tomdemuyt 2015年

1
通过运行上面的@zhanziyang的代码来进行测试非常容易。您可以完全向String对象(包装器)添加新属性,这意味着它不是不可变的(默认情况下;与其他任何对象一样,您可以对其进行调用Object.freeze以使其变为不可变的)。但是,无论是否包含在String对象包装器中,原始字符串值类型始终是不可变的。
马克·里德

3

字符串是不可变的 -它们不能更改,我们只能制造新的字符串。

例:

var str= "Immutable value"; // it is immutable

var other= statement.slice(2, 10); // new string

1

关于您在ASP.NET Ajax中关于StringBuilder的问题(在对Ash的回应的评论中),专家似乎对此表示不同意见。

克里斯汀·温兹(Christian Wenz)在他的《Programming ASP.NET AJAX(O'Reilly)》一书中说:“这种方法对内存没有任何可测量的影响(实际上,该实现似乎比标准方法稍慢一些)”。

另一方面,Gallo等人在他们的书《ASP.NET AJAX in Action(Manning)》中说:“当要连接的字符串数量较大时,字符串构建器将成为避免性能大幅下降的重要对象。”

我猜您需要进行自己的基准测试,并且浏览器之间的结果可能也会有所不同。但是,即使它不能提高性能,对于习惯使用C#或Java之类的StringBuilders进行编码的程序员来说,它仍然可能被认为“有用”。


1

这是一个较晚的帖子,但是我在答案中找不到一本好书。

除了可靠的书,这是肯定的:

字符串在ECMAScript中是不可变的,这意味着一旦创建它们,它们的值就无法更改。要更改变量保存的字符串,必须销毁原始字符串,并用另一个包含新值的字符串填充该变量... — Web开发人员的专业JavaScript,第3版,第43页

现在,引用Rhino书摘录的答案是关于字符串不变性的,但是说“字符串是通过引用而不是值分配的”是错误的。(可能原本是要相反地使用这些词)。

在“专业JavaScript”的“原始和参考值”一章中澄清了“参考/值”的误解:

五个基本类型... [是]:未定义,空,布尔值,数字和字符串。据说这些变量是按值访问的,因为您正在操纵存储在变量中的实际值。 — Web开发人员的专业JavaScript,第3版,第85页

对象相反:

当操作一个对象时,实际上是在对该对象的引用而不是实际对象本身上进行引用。因此,据说这些值是通过引用访问的。— Web开发人员的专业JavaScript,第3版,第85页


FWIW:Rhino的书可能意味着内部/实现字符串分配是在存储/复制指针(而不是复制字符串的内容)。根据其后的文字,这看起来并不意外。但我同意:他们滥用了“参考”一词。它不是“通过引用”,仅仅是因为实现传递了指针(以提高性能)。Wiki-评估策略是有关此主题的有趣文章。
制造商史蒂夫(Steve),


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.