为什么String.prototype的方法可以用于字符串字面量?

8
这个问题源于另一个问题,它涉及使用字符串字面量时console.dir的行为。特别地,请参考我的回答中的评论。
众所周知,在JavaScript中,String对象有许多方法。这些方法都在String.prototype对象上定义。例如String.prototype.toUpperCase。因此我们可以这样做:
var s = new String("hello"),
    s2 = s.toUpperCase();      //toUpperCase is a method on String.prototype

然而,我们还可以这样做:
var s = "hello",               //s is a string literal, not an instance of String
    s2 = s.toUpperCase();

显然,当您在字符串文字上调用String.prototype的方法时,JavaScript解释器正在进行某种形式的转换/强制类型转换。但是,在规范中我找不到任何关于此的参考资料。
这是有道理的,否则您必须明确地将每个字符串文字转换为String对象,然后才能使用任何方法,这将非常麻烦。
因此,我的问题是,这个功能在哪里描述,并且我是否正确地假设文字值被暂时转换为String的实例?我是不是过度思考,错过了一些显而易见的东西?

1
顺便说一下,new String(value) 这种写法是完全没用的...不要使用它。 - Šime Vidas
我知道它是这样的。问题是为什么。在规范中描述了这个吗? - James Allardice
1
一个字符串字面量是一个字符串对象:typeof "hello" === "string" 返回 true。这是否解决了问题? - bbg
1
@bbg - 这并不表明它是一个“String”对象:“typeof (new String("hello")) === "object"”。 - James Allardice
@missingno - 我首先查看了关于String构造函数、String对象和string类型的部分,但它们并没有澄清任何问题。请参考被接受的答案中解释这种行为的规范部分。 - James Allardice
显示剩余2条评论
4个回答

11

在这里定义:

当V是具有基本值属性引用时,GetValue调用[[Get]]内部方法。使用基础值作为其this值,并将属性P作为其参数调用它。采取以下步骤:

  1. 让O成为ToObject(base)。
  2. 让desc成为使用属性名称P调用O的[[GetProperty]]内部方法的结果。
  3. 如果desc未定义,则返回undefined。
  4. 如果IsDataDescriptor(desc)为true,则返回desc.[[Value]]。
  5. 否则,IsAccessorDescriptor(desc)必须为true,因此让getter成为desc.[[Get]]。
  6. 如果getter未定义,则返回undefined。
  7. 提供base作为this值,不提供参数调用getter的[[Call]]内部方法的结果。

注意:在步骤1中可能创建的对象无法在上述方法之外访问。实现可能选择避免实际创建对象。使用此内部方法进行实际属性访问可能具有可见效果的唯一情况是当它调用存取器函数时。

来源:http://es5.github.com/#x8.7.1

字符串基本值在第1步中被强制转换为对象。


例1

var str = 'some string';
str = str.toUpperCase();

在这里,表达式str.toUpperCase的求值方式遵循11.2.1 属性访问器中定义的语义:

  1. 根据标识符解析(见10.2.2.1 GetIdentifierReference),计算标识符str。结果是一个引用,其基础值为当前词法环境的环境记录,其引用名称为"str"。此引用是baseReference
  2. 通过执行GetValue(baseReference)来确定baseValue的值。由于baseReference不是属性引用(其基础值不是对象或原始值,而是环境记录),因此调用GetBindingValue()方法以检索引用的值。该方法返回局部变量str的值,即原始字符串值'some string'。此值是baseValue
  3. propertyNameValue的值求值为原始字符串值'toUpperCase'。(出于简单起见,我缩短了这个过程。)
  4. 创建一个新的引用,其基础值为baseValue,其引用名称为propertyNameValue

因此,此过程涉及两个引用:

  • str(基础值:环境记录,引用名称:'str'
  • str.toUpperCase(基础值:'some string',引用名称:'toUpperCase'

最后,在后者的引用上执行调用运算符()。该引用的值根据本答案顶部定义的语义确定。

示例2

var str = 'some string'.toUpperCase();

这里,表达式'some string'.toUpperCase根据与示例1相同的“属性访问器”语义进行评估:

  1. 字符串文字'some string'显然会评估为原始的String值'some string'。这是baseReference。(不要被名称所困惑 - 这是字符串值,而不是引用。)
  2. 通过执行GetValue(baseReference)来确定baseValue。由于baseReference不是引用,因此该方法仅返回参数值,即baseValue = baseReference

正如在示例1中一样,可以看到,baseValue也是原始的String值'some string'。步骤3和4等同于示例1中的步骤3和4。

因此,标识符引用str和字符串文字'some string'都评估为相同的值 - 原始的String值'some string' - 并且该值用作新引用的baseValue,然后使用()进行调用。由于该引用具有原始基础值,因此定义在我的答案开头的语义适用。


+1 - 我正在寻找那个! - Mike Christensen
啊,这一切都很合理 :) 我同意Felix的观点,做得非常好!再次感谢。 - James Allardice
@ŠimeVidas - 字符串字面量是如何转换为引用的?换句话说,为什么我们可以执行 "hello".toUpperCase(); 而不需要通过将字面量分配给变量(标识符)创建引用呢? - James Allardice
@FelixKling 我在上面的评论中犯了一个错误。标识符s评估为一个引用,但该引用的基本值不是原始字符串'some string',而是当前词法环境的环境记录。这个s.toUpperCase评估为第二个引用,this引用的基本值是原始字符串值... - Šime Vidas
@ŠimeVidas - 哇,谢谢你提供如此全面的答案!非常感激。 - James Allardice
显示剩余3条评论

2

这正是我所猜测的,但我仍然找不到在ECMAScript规范中描述这种行为的地方。你知道它是否被描述在那里吗? - James Allardice

2

根据参考文献,字面量将被转换为对象:

双引号或单引号括起来的字符串字面量以及在非构造函数上下文中(即不使用new关键字)从String调用返回的字符串都是原始字符串。JavaScript会自动将原始字符串和String对象进行转换,因此可以使用String对象方法处理原始字符串。


这正是我所猜测的,但我仍然找不到在ECMAScript规范中描述这种行为的地方。你知道它是否被描述在那里吗? - James Allardice

0

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