为什么有两种类型的JavaScript字符串?

38

这个问题困扰我很久了。我不知道所有浏览器是否都是这种情况(我没有其他可测试的浏览器),但至少Firefox有两种字符串对象。

打开Firebug控制台,尝试以下操作:

>>> "a"
"a"
>>> new String("a")
String { 0="a"}

如您所见,Firefox对new String("a")"a"进行了不同的处理。然而,在其他方面,两种类型的字符串似乎表现相同。例如,有证据显示它们都使用相同的原型对象:

>>> String.prototype.log = function() { console.log("Logged string: " + this); }
function()
>>> "hello world".log()
Logged string: hello world
>>> new String("hello world").log()
Logged string: hello world

显然,这两个是相同的。也就是说,直到你要求类型时才不同。

>>> typeof("a")
"string"
>>> typeof(new String("a"))
"object"

我们还可以注意到,当this是一个字符串时,它总是以对象形式出现:

>>> var identity = function() { return this }
>>> identity.call("a")
String { 0="a"}
>>> identity.call(new String("a"))
String { 0="a"}

更进一步,我们可以看到非对象字符串表示不支持任何附加属性,而对象字符串则支持:

>>> var a = "a"
>>> var b = new String("b")
>>> a.bar = 4
4
>>> b.bar = 4
4
>>> a.bar
undefined
>>> b.bar
4

还有一个有趣的事实!您可以使用 toString() 函数将字符串对象转换为非对象字符串:

>>> new String("foo").toString()
"foo"

从来没有想过调用String.toString()可能会有用!不管怎样。

那么所有这些实验都引出了一个问题:为什么JavaScript中存在两种字符串?


评论显示每个原始的JavaScript类型(包括数字和布尔值)也是如此。


2
有两种类型的数字,也就是整数和浮点数。 - Pointy
1
还有两种布尔类型:true === new Boolean(true); // false - Raynos
4
我们可以这样说,JavaScript是一种二进制类型的语言。 - BoltClock
换句话说,每个原始类型都有一个包装类型(我认为完全没有用)。 - Ruan Mendes
3个回答

14

在JavaScript中有两种类型的字符串--字面量字符串和String对象。它们的行为略有不同。两者之间的主要区别在于您可以向String对象添加附加的方法和属性。例如:

var strObj = new String("object mode");
strObj.string_mode = "object"
strObj.get_string_mode = function() { return this.string_mode; }

// this converts it from an Object to a primitive string:
str = strObj.toString();

字符串字面值只是暂时转换为字符串对象以执行任何核心方法。

其他数据类型也适用于同样的概念。 这里有更多关于原始数据类型和对象的信息

编辑

如评论中所述,字符串字面值不是原始字符串,而是“字面常量,其类型是内置的基本[字符串]值”,引用此来源


5
与其说是字面值,不如说是字符串原语。请参考我的对Raynos的回答的评论。 - Tim Down
术语“字面量”和“原始类型”似乎被宽泛地使用,无论是否按照规定。维基百科在同一思路中使用了这两个术语:“JavaScript提供了一个带有true和false字面量的布尔数据类型。typeof运算符返回这些原始类型的字符串'boolean'。” - Kelly
@TimDown 它是字符串的值,而不是基本类型。请参阅ES5规范。 - Raynos
@Raynos:primitive 是一个非常可接受的术语,它被应用于 ECMAScript 5 规范中的字符串、数字和布尔值的集合。还有 ToPrimitive 内部方法和 [[PrimitiveValue]] 属性,前者返回一个原始值,可以是任何原始类型之一。因此,字符串值或字符串原语都应该是可接受的术语。 - Andy E
@AndyE 这是对象的原始值。这有些微妙的不同,但它是一个可接受的术语。 - Raynos
显示剩余2条评论

2
你正在比较字符串值和字符串对象。 "a" 是一个字符串值。 "a" === "a"; // true new String("a") 是一个字符串对象。 new String("a") === new String("a"); // false 它们都是字符串。 "a" 只是获取字符串值 "a",而 new String("a") 则创建了一个新的字符串对象,其中内部具有字符串值 "a"。

创建可能是错误的词。它返回一个字符串字面量。 - Raynos
如果“创建”不是正确的动词,那么它如何使“foobar”存在?此外,您认为这些“全局静态”字符串是否会被垃圾回收/回收利用? - zneak
2
略微不准确:区别在于字符串原始值和String对象之间。字符串字面量确实会产生字符串原始值,但并不是唯一的方式。任何JavaScript对象上的toString()方法也会产生一个字符串原始值。 - Tim Down
2
同意,我会修正术语。 "字面量" 定义了创建某物而不使用构造函数所需的语法。 字面量可以创建原始值或对象(请参见对象/数组字面量),正如 @Tim Down 所说,原始值可以从对象中创建。 - Andy E
1
@TimDown @AndyE 更改了措辞以匹配 ES5 规范。 - Raynos
显示剩余2条评论

0

另一个需要记住的重要事情是:

typeof "my-string" // "string"
typeof String('my-string') // 'string'
typeof new String("my-string") // "object". 

因此,在测试一个参数或变量是否是字符串时,你至少需要执行以下操作。

function isString(arg) {
  if (!arg) {
    return false;
  }
  return typeof arg == "string" || arg.constructor == String;
}

如果您从另一个框架/iframe传递一个字符串对象,上述函数仍将失败。也就是说:

frames[0].myStringVar.constructor != frames[1].myStringVar.constructor

这是因为每个窗口上下文的构造函数String都不同。因此,一个万无一失的isString方法应该是:

function isString(obj) {
   return Object.prototype.toString.call(obj) == "[object String]";
}

1
你最近的 isString 函数不是更像是一个 toString 函数吗? - zneak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接