JavaScript 字符串是不可变的吗?我需要 JavaScript 中的“字符串生成器”吗?
Posted
技术标签:
【中文标题】JavaScript 字符串是不可变的吗?我需要 JavaScript 中的“字符串生成器”吗?【英文标题】:Are JavaScript strings immutable? Do I need a "string builder" in JavaScript? 【发布时间】:2010-09-08 05:57:31 【问题描述】:javascript 使用不可变字符串还是可变字符串?我需要一个“字符串生成器”吗?
【问题讨论】:
是的,y 是不可变的,您需要某种“字符串生成器”。阅读此blog.codeeffects.com/Article/String-Builder-In-Java-Script 或此codeproject.com/KB/scripting/stringbuilder.aspx 有趣,这些例子与我在回答中的发现相矛盾。 【参考方案1】:它们是不可变的。您不能使用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.push
和Array.join
使用数组索引来避免Array.push
,然后使用Array.join
直字符串连接
然后我通过将它们抽象为StringBuilderConcat
、StringBuilderArrayPush
和StringBuilderArrayIndex
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.join
在 FF 中仅稍慢一点,但在 Chrome 中则慢很多 (3x)。
Chrome 排名第三,但字符串 concat 比 Array.join 快 3 倍
创建 StringBuilder 似乎对性能影响不大。
希望其他人觉得这很有用
不同的测试用例
由于@RoyTinker 认为我的测试存在缺陷,因此我创建了一个新案例,它不会通过连接相同的字符串来创建大字符串,而是在每次迭代时使用不同的字符。字符串连接似乎仍然更快或同样快。让我们运行这些测试。
我建议每个人都应该继续考虑其他方法来测试它,并随时在下面添加指向不同测试用例的新链接。
http://jsperf.com/string-concat-without-sringbuilder/7
【讨论】:
@Juan,您要求我们访问的链接将 112 个字符的字符串连接了 30 次。这是另一个可能有助于平衡事情的测试 - Array.join 与 20,000 个 不同 1-char 字符串上的字符串连接(在 IE/FF 上连接速度要快得多)。 jsperf.com/join-vs-str-concat-large-array @RoyTinker Roy,哦,Roy,您的测试作弊,因为您在测试设置中创建了数组。这是使用不同字符的真实测试jsperf.com/string-concat-without-sringbuilder/7 随意创建新的测试用例,但创建数组是测试本身的一部分 @JuanMendes 我的目标是将测试用例缩小到严格比较join
与字符串连接,从而在测试之前构建数组。如果该目标被理解,我认为这不是作弊(并且join
在内部枚举数组,因此从join
测试中省略for
循环也不是作弊)。
@RoyTinker 是的,任何字符串构建器都需要构建数组。问题是关于是否需要字符串生成器。如果您已经在数组中包含字符串,那么对于我们在这里讨论的内容来说,它不是一个有效的测试用例
@JuanMendes - 好的,点了。我的测试假设数组已经存在,而在评估字符串构建器时你不能假设。【参考方案2】:
来自rhino book:
在 JavaScript 中,字符串是不可变的对象,这意味着 其中的字符可能不会改变,并且任何操作 字符串实际上创建了新的字符串。字符串由 参考,而不是价值。一般来说,当一个对象由 引用,通过一个引用对对象所做的更改将是 通过对该对象的所有其他引用可见。因为字符串 不能更改,但是,您可以有多个引用 字符串对象,不用担心字符串值会改变 你知道吗
【讨论】:
犀牛书相应章节的链接:books.google.com/… Rhino 书中的引用(以及因此的答案)在这里是错误的。在 JavaScript 中,字符串是原始值类型和 not 对象(spec)。事实上,在 ES5 中,它们是与null
undefined
number
和 boolean
并列的仅有的 5 种值类型之一。字符串由 value 和 not 通过引用分配,并按原样传递。因此,字符串不仅是不可变的,它们还是一个值。将字符串 "hello"
更改为 "world"
就像决定从现在开始数字 3 就是数字 4……这是没有意义的。
是的,就像我的评论说字符串是不可变的,但它们不是引用类型,也不是对象——它们是原始值类型。一个简单的方法来查看它们不是尝试将属性添加到字符串然后读取它:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
@VidarS.Ramdal 不,String
使用字符串构造函数创建的对象是 JavaScript 字符串值的 包装器。您可以使用.valueOf()
函数访问装箱类型的字符串值——Number
对象和数字值也是如此。重要的是要注意使用new String
创建的String
对象不是实际的字符串,而是字符串周围的wrappers 或boxes。见es5.github.io/#x15.5.2.1。关于事物如何转换为对象,请参阅es5.github.io/#x9.9
至于为什么有些人说字符串是对象,它们可能来自 Python 或 Lisp 或任何其他语言,其规范使用“对象”一词来表示任何类型的数据(甚至整数)。他们只需要阅读 ECMA 规范如何定义这个词:“对象类型的成员”。此外,根据不同语言的规范,即使是“价值”这个词也可能意味着不同的东西。【参考方案3】:
性能提示:
如果您必须连接大字符串,请将字符串部分放入数组并使用Array.Join()
方法获取整个字符串。对于连接大量字符串,这可以快很多倍。
JavaScript 中没有StringBuilder
。
【讨论】:
我知道没有stringBuilder,msAjax有,我只是在思考它是否有用 这与字符串是否不可变有什么关系? @docgnome:因为字符串是不可变的,字符串连接需要创建比 Array.join 方法更多的对象 根据上面 Juan 的测试,字符串连接实际上在 IE 和 Chrome 中都更快,而在 Firefox 中则更慢。 考虑更新您的答案,这可能在很久以前是正确的,但现在不是了。见jsperf.com/string-concat-without-sringbuilder/5【参考方案4】:只是为了澄清像我这样的简单头脑(来自MDN):
不可变对象是对象一旦创建就不能改变状态。
字符串和数字是不可变的。
不可变意味着:
您可以使变量名指向一个新值,但之前的值仍保存在内存中。因此需要垃圾收集。
var immutableString = "Hello";
// 在上面的代码中,创建了一个带有字符串值的新对象。
immutableString = immutableString + "World";
// 我们现在将“世界”附加到现有值。
这看起来我们正在改变字符串“immutableString”,但我们没有。而是:
在“immutableString”附加一个字符串值时,会发生以下事件:
检索到“immutableString”的现有值 “World”附加到“immutableString”的现有值 然后将结果值分配给新的内存块 “immutableString”对象现在指向新创建的内存空间 之前创建的内存空间现在可用于垃圾回收。
【讨论】:
如果你这样做var immutableString = "Hello"; immutableString="world"
会不会一样?我的意思是为变量分配一个全新的值
当然可以。
感谢 katinka,但我的意思是,如果您分配一个全新的值,您会“改变字符串”吗?或者应用你在这里解释得很好的相同?如果它看起来你正在变异它,但你不是......
您正在用一个新的内存字段替换它。旧字符串被丢弃。【参考方案5】:
字符串类型的值是不可变的,但是使用String()构造函数创建的String对象是可变的,因为它是一个对象,你可以给它添加新的属性。
> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
[String: 'test'] newProp: 'some value'
同时,虽然您可以添加新属性,但不能更改已经存在的属性
A screenshot of a test in Chrome console
总之, 1.所有字符串类型值(原始类型)都是不可变的。 2、String对象是可变的,但它所包含的字符串类型值(原始类型)是不可变的。
【讨论】:
Javascript String 对象是不可变的developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures @prasun 但在该页面中它说:“除了对象之外的所有类型都定义了不可变的值(值,无法更改)。”字符串对象是对象。如果您可以在其上添加新属性,它又如何是不可变的? 阅读“字符串类型”部分。 Javascript 的字符串链接指向原始和对象,后来它说“JavaScript 字符串是不可变的”。看起来文档在这个主题上并不清楚,因为它在两个不同的注释上存在冲突new String
在不可变字符串周围生成可变包装器
上面运行@zhanziyang的代码很容易测试。您完全可以向String
对象(包装器)添加新属性,这意味着它不是 不可变的(默认情况下;像任何其他对象一样,您可以在其上调用Object.freeze
以使其不可变)。但是原始字符串值类型,无论是否包含在 String
对象包装器中,始终是不可变的。【参考方案6】:
字符串是不可变的——它们不能改变,我们只能创建新的字符串。
例子:
var str= "Immutable value"; // it is immutable
var other= statement.slice(2, 10); // new string
【讨论】:
【参考方案7】:关于您对 ASP.NET Ajax 中的 StringBuilder 的问题(在您对 Ash 的回复的评论中),专家们似乎不同意这一点。
Christian Wenz 在他的书 Programming ASP.NET AJAX (O'Reilly) 中说:“这种方法对内存没有任何可测量的影响(实际上,实现似乎慢了一点)比标准方法)。”
另一方面,Gallo 等人在他们的著作 ASP.NET AJAX in Action (Manning) 中说:“当要连接的字符串数量更大时,字符串构建器成为避免性能大幅下降。”
我想您需要自己进行基准测试,结果也可能因浏览器而异。但是,即使它没有提高性能,对于习惯于用 C# 或 Java 等语言使用 StringBuilder 进行编码的程序员来说,它仍然可能被认为是“有用的”。
【讨论】:
【参考方案8】:这是一篇迟到的帖子,但我没有在答案中找到好的书名。
除了可靠的书外,这是肯定的:
字符串在 ECMAScript 中是不可变的,这意味着一旦它们被创建,它们的值就不能改变。要更改变量保存的字符串,必须销毁原始字符串并用另一个包含新值的字符串填充变量... —面向 Web 开发人员的专业 JavaScript,第 3 版,第 43 页
现在,引用 Rhino 书摘录的答案是关于字符串不变性的正确答案,但错误地说“字符串是通过引用分配的,而不是通过值分配的。” (可能他们最初的意思是把这些词反其道而行之)。
在“Professional JavaScript”,名为“Primitive and Reference values”的章节中澄清了“引用/值”的误解:
五种基本类型...[是]:未定义、空值、布尔值、数字和字符串。据说这些变量是按值访问的,因为您正在操作存储在变量中的实际值。 —面向 Web 开发人员的专业 JavaScript,第 3 版,第 85 页
这与对象相反:
当您操作一个对象时,您实际上是在处理对该对象的引用,而不是实际的对象本身。出于这个原因,这些值被认为是通过引用访问的。—面向 Web 开发人员的专业 JavaScript,第 3 版,第 85 页
【讨论】:
FWIW:Rhino 书可能意味着 内部/实现 字符串赋值正在存储/复制 指针(而不是复制细绳)。根据之后的文字,这看起来不像是意外。但我同意:他们滥用“通过引用”一词。它不是“通过引用”仅仅因为实现传递了指针(为了性能)。 Wiki - evaluation strategy 是关于这个主题的有趣读物。【参考方案9】:JavaScript 字符串确实是不可变的。
【讨论】:
文档链接?我找不到这方面的任何细节。 “尝试设置单个字符不起作用” -- developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…【参考方案10】:Javascript 中的字符串是不可变的
【讨论】:
以上是关于JavaScript 字符串是不可变的吗?我需要 JavaScript 中的“字符串生成器”吗?的主要内容,如果未能解决你的问题,请参考以下文章