ECMAScript 规范的哪一部分要求 String 类型是不可变的?

What part of ECMAScript spec mandates that String type is immutable?

我知道 Javascript 中的 String 原始类型是不可变的,这是通过实践和口耳相传。但是 ECMA-262 中的哪些规则组合使其如此呢?更具体地说,为什么以下示例中的第二行静默不执行任何操作?

const str = 'abc';
str[1] = '8';
console.log(str); // prints unmodified "abc", not "a8c".

Section 6.1.4描述了String数据的内部构成。里面没有修改数据的内容,至少我看了三次也没找到。

Section 13.15.2描述了赋值的语义。它不包含任何特定数据类型的任何异常。

Section 13.3.2.1 描述了 属性 存取运算符的语义。它不包含任何特定数据类型的任何异常。

那么,字符串数据类型在Javascript中究竟是如何定义为不可变的呢?

EMCAScript 规范对此保持沉默,正如它 silent on object identity(即对象可变性)一样。所以是的,它更多的是关于“通过实践和口耳相传”,但我想一个符合规范的实现可以改变字符串的内存表示背景(或者甚至提供 API 作为扩展)。

但是,您可以从链接的 §6.1.4 "The String Type" 的措辞中推断出字符串的不变性:

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 253 - 1 elements.

这是一个非常数学化的定义,在数学中,总是不可变的。此外,我们可以观察到在 ECMAScript 中根本没有任何操作会改变这样的值。

虽然该定义仅适用于原始值,但不适用于 String 包装此类值并可以具有附加(可变)属性的对象。同样,没有任何操作会更改 a String instance. This is even explicitly described in the section on String Exotic Objects:

[[StringData]] 内部插槽

A String object is an exotic object that encapsulates a String value and exposes virtual integer-indexed data properties corresponding to the individual code unit elements of the String value. String exotic objects always have a data property named "length" whose value is the number of code unit elements in the encapsulated String value. Both the code unit data properties and the "length" property are non-writable and non-configurable.

这也是您在示例代码中观察到的。赋值 str[1] = …String's [[DefineOwnProperty]] internal method, which finds that the 1 property is non-writable, rejecting a change. The PutValue abstract operation 然后将在严格模式下抛出异常(或在 sloppy 模式下忽略失败)。

编辑:我觉得我原来的回答错得不能再挽回了,但是既然有这么好的回复,我也不想直接删掉。我会尝试修复它。

试图说在赋值的左侧只有[ ]、按键访问对象和解构两种选择。字符串是没有文本值中每个索引键的对象。解构表达式也不适合,即使那样它也会是 undefined 的新表达式(值),因此不会修改原始字符串。这可能就是为什么没有错误但似乎什么也没做的原因。它修改未存储在任何地方的表达式的(临时)值。

标准的

部分10.4.3.5可能是最接近识别规则的东西,说结果是[[Writable]]: false,但由于它仍然是一个表达式,它代表的值仍然可以是分配给某物,即使如果不分配该值将被丢弃。

有趣的是,let B = 'abc'[1] = 'B' 会按预期将 'B' 赋值给 B,但不会修改字符串,无论它是字符串常量还是变量。所以 let A='abc'; let B = A[1] = 'B' 具有相同的效果。同样,因为在这种情况下它是 [1] 没有键的基本类型,所以新表达式更多的是一个新值,而不是针对存储字符串的原始内存。

更有趣:let B = undefined = 'B' 是否成功将'B'分配给B。我不太确定如何,但我认为这是相同的情况因为 {}[1] 即使 undefined 是一个可以分配给 B 的实际值。我认为这意味着如果计算结果为 undefined,则一系列赋值中的中间值存在异常, 并且不会将 undefined 传递给左侧的下一个表达式。

我认为这与 undefined = 'B' 没有做任何事情的情况相同,但是 left-hand 侧的 value 仍然是 'B',如果分配到某个地方。我可以预测调用 func(undefined = 3) 实际上会将 3 传递给函数。


原始答案:我现在删除它真是大错特错。感谢@hijarian 用他对 [ ] 运算符的评论唤醒了我。