基本上,因为
规范是这样规定的:
字符串值
有限的、有序的零个或多个16位无符号整数值的原始值
规范还定义了String对象,与原始字符串不同。 (类似地,还有原始的number
、boolean
和symbol
类型,以及Number和Boolean和Symbol对象。)
原始字符串遵循其他原始类型的所有规则。 在语言层面上,它们的处理方式与原始数字和布尔值完全相同。 就所有目的而言,它们就是原始值。 但是正如您所说,如果a = b
字面上复制b
中的字符串并将该副本放入a
将是疯狂的。 实现不必这样做,因为原始字符串值是不可变的(就像原始数字值一样)。 您无法更改字符串中的任何字符,只能创建新字符串。 如果字符串是可变的,则实现必须在执行a = b
时进行复制(但如果它们是可变的,则规范将编写不同)。
请注意,原始字符串和String对象确实是不同的东西:
const s = "hey";
const o = new String("hey");
s.foo = "bar";
console.log(s.foo);
o.foo = "bar";
console.log(o.foo);
因此,为什么需要原始字符串?你可能要问 Brendan Eich(他在 Twitter 上会比较负责),但我认为这是为了避免等价操作符(
==
,
===
,
!=
, 和
!==
)的定义既可以被对象类型用于自身目的重载,也可以为字符串特殊处理。
那么为什么需要字符串对象?有了 String 对象(以及 Number 对象、Boolean 对象和 Symbol 对象),并规定何时创建基本类型的临时对象版本,就可以在基本类型上定义方法。当你执行以下操作时:
console.log("example".toUpperCase());
从规范方面来说,一个字符串对象是通过GetValue操作 创建的,然后在该对象上查找并调用了toUpperCase
属性(如上所述)。因此,原始字符串从String.prototype
和Object.prototype
获得它们的toUpperCase
(和其他标准方法)。但是创建的临时对象对代码不可访问,除非在一些边缘情况下,¹JavaScript引擎可以避免在这些边缘情况之外直接创建对象。好处是新方法可以添加到String.prototype
中并在原始字符串上使用。
¹ “什么是边缘情况?”我听到你问道。我能想到的最常见的情况是在松散模式下的代码中向String.prototype
(或类似对象)中添加自己的方法:
Object.defineProperty(String.prototype, "example", {
value() {
console.log(`typeof this: ${typeof this}`);
console.log(`this instance of String: ${this instanceof String}`);
},
writable: true,
configurable: true
});
"foo".example();
在这种情况下,JavaScript引擎被迫创建String对象,因为在宽松模式下,this
不能是原始类型。
严格模式可以避免创建对象,因为在严格模式下,this
不需要是对象类型,它可以是一个原始类型(在这种情况下,是一个原始字符串):
"use strict";
Object.defineProperty(String.prototype, "example", {
value() {
console.log(`typeof this: ${typeof this}`);
console.log(`this instance of String: ${this instanceof String}`);
},
writable: true,
configurable: true
});
"foo".example();