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

IT技术 javascript string
2021-01-20 15:06:43

javascript 使用不可变字符串还是可变字符串?我需要“字符串生成器”吗?

6个回答

它们是不可变的。您不能使用类似var myString = "abbdef"; myString[2] = 'c'. 字符串操作方法如trim,slice返回新字符串。

同理,如果你有两个对同一个字符串的引用,修改一个不会影响另一个

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
  • 直接字符串连接

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

如果您不想点击链接,这里有一些数字(Ma5rch 2018 中的最新更新)。每个测试的数量是 1000 次操作/秒(越高越好

浏览器 指数 康卡特 指数 SBP推送 SBConcat
铬 71.0.3578 988 1006 2902 963 1008 2902
火狐 65 1979年 1902 2197 1917年 1873 1953年
边缘 593 373 952 361 415 444
炸药11 655 532 761 537 567 387
歌剧 58.0.3135 1135 1200 4357 1137 1188 4294

发现

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

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

  • Firefox 位居第二,Array.join在 FF 中仅稍慢,但在 Chrome 中慢得多(3 倍)。

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

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

希望其他人觉得这很有用

不同的测试用例

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

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

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

@RoyTinker Roy,哦,Roy,你的测试是在作弊,因为你在测试设置中创建了数组。这是使用不同字符的真实测试jsperf.com/string-concat-without-sringbuilder/7随意创建新的测试用例,但创建数组是测试本身的一部分
2021-03-23 15:06:43
@JuanMendes - 好的,重点来了。我的测试假设数组已经存在,在评估字符串构建器时不能假设。
2021-03-25 15:06:43
@Juan,您要求我们访问的链接将 112 个字符的字符串连接了 30 次。这是另一个可能有助于平衡事物的测试 - Array.join 与 20,000 个不同1-char 字符串上的字符串连接(IE/FF 上的连接速度要快得多)。 jsperf.com/join-vs-str-concat-large-array
2021-04-02 15:06:43
@RoyTinker 是的,任何字符串构建器都需要构建数组。问题是是否需要字符串生成器。如果您已经在数组中有字符串,那么它不是我们在这里讨论的有效测试用例
2021-04-02 15:06:43
@JuanMendes 我的目标是将测试用例缩小到join与字符串连接的严格比较,从而在测试之前构建数组。如果该目标被理解,我不认为这是作弊(并join在内部枚举数组,因此forjoin测试中省略循环也不是作弊)。
2021-04-04 15:06:43

来自犀牛书

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

链接到犀牛书的相应部分:books.google.com/...
2021-03-10 15:06:43
Rhino 书的引用(以及这个答案)在这里错误的在 JavaScript 中,字符串是原始值类型,而不是对象(spec)事实上,从 ES5 开始,它们是与null undefined number一起的仅有的 5 种值类型之一boolean字符串按不是按引用分配,并按原样传递。因此,字符串不仅是不可变的,它们还是一个更改字符串"hello""world"就像决定,从现在起的3号是多少4 ...这没有任何意义。
2021-03-18 15:06:43
至于为什么有人说字符串是对象,它们可能来自 Python 或 Lisp 或任何其他语言,其中其规范使用“对象”一词来表示任何类型的数据(甚至整数)。他们只需要阅读 ECMA 规范如何定义这个词:“对象类型的成员”。此外,根据不同语言的规范,即使是“value”这个词也可能意味着不同的东西。
2021-03-20 15:06:43
@VidarS.Ramdal 不,String使用字符串构造函数创建的对象是JavaScript 字符串值的包装器您可以使用该.valueOf()函数访问装箱类型的字符串值- 对于Number对象和数字值也是如此重要的是要注意String使用创建的对象new String不是实际的字符串,而是字符串周围的包装器盒子参见es5.github.io/#x15.5.2.1关于事物如何转化为对象见es5.github.io/#x9.9
2021-03-21 15:06:43
是的,就像我的评论所说的字符串不可变的,但它们不是引用类型,也不是对象——它们是原始值类型。查看它们两者的简单方法是尝试将属性添加到字符串然后读取它:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
2021-03-31 15:06:43

性能提示:

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

StringBuilderJavaScript 中没有

这与字符串是否不可变有什么关系?
2021-03-11 15:06:43
考虑更新你的答案,很久以前它可能是真的,但现在不是了。参见jsperf.com/string-concat-without-sringbuilder/5
2021-03-12 15:06:43
我知道没有 stringBuilder,msAjax 有一个,我只是在考虑它是否有用
2021-03-14 15:06:43
@docgnome:因为字符串是不可变的,所以字符串连接需要创建比 Array.join 方法更多的对象
2021-04-04 15:06:43
根据 Juan 上面的测试,字符串连接实际上在 IE 和 Chrome 中都更快,而在 Firefox 中更慢。
2021-04-09 15:06:43

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

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

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

不可变意味着:

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

var immutableString = "Hello";

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

immutableString = immutableString + "World";

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

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

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

  1. 检索“immutableString”的现有值
  2. “World”附加到“immutableString”的现有值
  3. 然后将结果值分配给新的内存块
  4. “immutableString”对象现在指向新创建的内存空间
  5. 以前创建的内存空间现在可用于垃圾收集。
谢谢 katinka 但我的意思是,如果你分配一个全新的值,你会“改变字符串”吗?或者适用于您在这里解释得很好的情况?如果它看起来你正在变异它,但你不是......
2021-03-13 15:06:43
您正在用新的内存字段替换它。旧字符串被丢弃。
2021-03-14 15:06:43
当然,你可以这样做。
2021-03-24 15:06:43
如果你这样做会一样var immutableString = "Hello"; immutableString="world"吗?我的意思是为变量分配一个全新的值
2021-03-29 15:06:43

string 类型的值是不可变的,但是使用 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.所有的字符串类型值(primitive type)都是不可变的。2. String 对象是可变的,但它包含的字符串类型值(原始类型)是不可变的。

new String 围绕不可变字符串生成可变包装器
2021-03-12 15:06:43
阅读“字符串类型”部分。Javascript 的字符串链接同时指向原始对象和对象,然后它说“JavaScript 字符串是不可变的”。该文档似乎在此主题上不清楚,因为它在两个不同的注释中发生冲突
2021-03-14 15:06:43
@prasun 但在该页面中它说:“除对象之外的所有类型都定义了不可变值(值,无法更改)。”字符串对象是对象。如果您可以在其上添加新属性,它又如何是不可变的?
2021-03-18 15:06:43
2021-04-04 15:06:43
通过运行上面@zhanziyang 的代码很容易测试。您可以完全向String对象(包装器)添加新属性,这意味着它不是不可变的(默认情况下;就像任何其他对象一样,您可以调用Object.freeze它以使其不可变)。但是原始字符串值类型,无论是否包含在String对象包装器中,始终是不可变的。
2021-04-06 15:06:43