TLDR
类型强制,或隐式类型转换,启用弱类型并在整个 JavaScript 中使用。大多数运算符(严格相等运算符===
和!==
除外)和值检查操作(例如if(value)...
)将强制提供给它们的值,如果这些值的类型与操作不立即兼容。
用于强制值的精确机制取决于被评估的表达式。在问题中,正在使用加法运算符。
加法运算符将首先确保两个操作数都是原语,在这种情况下,这涉及调用valueOf
方法。toString
在此实例中未调用该方法,因为valueOf
对象上的重写方法x
返回一个原始值。
然后,因为问题中的一个操作数是字符串,所以两个操作数都被转换为字符串。这个过程使用抽象的内部操作ToString
(注:大写),区别于toString
对象(或其原型链)上的方法。
最后,将生成的字符串连接起来。
细节
在 JavaScript 中每种语言类型(即 Number、BigInt、String、Boolean、Symbol 和 Object)对应的每个构造函数对象的原型上,都有两个方法:valueOf
和toString
。
的目的valueOf
是检索与对象关联的原始值(如果有的话)。如果对象没有底层原始值,则简单地返回该对象。
如果valueOf
针对原语调用,则原语以正常方式自动装箱,并返回底层原语值。请注意,对于字符串,底层原始值(即由 返回的值valueOf
)是字符串表示本身。
以下代码显示该valueOf
方法从包装器对象返回底层原始值,并显示与原始值不对应的未修改对象实例如何没有要返回的原始值,因此它们仅返回自身。
console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)
toString
另一方面,的目的是返回对象的字符串表示。
例如:
console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'
对于大多数操作,JavaScript 会默默地尝试将一个或多个操作数转换为所需的类型。选择此行为是为了使 JavaScript 更易于使用。JavaScript最初没有异常,这可能也在这个设计决策中发挥了作用。这种隐式类型转换称为类型强制,它是 JavaScript 松散(弱)类型系统的基础。此行为背后的复杂规则旨在将类型转换的复杂性移入语言本身,并移出您的代码。
在强制过程中,可能发生两种转换模式:
- 将对象转换为原始类型(这可能涉及类型转换本身),以及
- 直接转换到一种特定类型的实例,使用该原语类型中的一个的构造函数对象(即
Number()
,Boolean()
,String()
等等)
转换为原始
当尝试将非原始类型转换为要操作的原始类型时,抽象操作ToPrimitive
将使用可选的“数字”或“字符串”“提示”调用。如果省略提示,则默认提示为“数字”(除非该@@toPrimitive
方法已被覆盖)。如果提示是“字符串”,则toString
首先尝试,valueOf
如果toString
没有返回原语,则第二次尝试。否则,反之亦然。提示取决于请求转换的操作。
加法运算符不提供任何提示,因此valueOf
首先尝试。减法运算符提供了“数字”的提示,因此valueOf
首先尝试。我可以在规范中找到提示为“字符串”的唯一情况是:
Object#toString
- 抽象操作
ToPropertyKey
,将参数转换为可用作属性键的值
直接类型转换
每个操作员都有自己的规则来完成他们的操作。加法运算符将首先用于ToPrimitive
确保每个操作数都是一个原语;然后,如果任一操作数是字符串,它就会故意调用ToString
每个操作数上的抽象操作,以提供我们期望的字符串连接行为。如果在该ToPrimitive
步骤之后,两个操作数都不是字符串,则执行算术加法。
与加法不同的是,减法运算符没有重载行为,因此会toNumeric
在每个操作数上调用,首先使用 将它们转换为原语ToPrimitive
。
所以:
1 + 1 // 2
'1' + 1 // '11' Both already primitives, RHS converted to string, '1' + '1', '11'
1 + [2] // '12' [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
1 + {} // '1[object Object]' {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
2 - {} // NaN {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a' // NaN `ToPrimitive` passed 'number' hint), Number('a'), NaN
+'' // 0 `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1' // -1 `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{} // NaN `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
1 + 'a' // '1a' Both are primitives, one is a string, String(1) + 'a'
1 + {} // '1[object Object]' One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + [] // '' Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
1 - 'a' // NaN Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
1 - {} // NaN One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - [] // 0 Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0
请注意,Date
内部对象是唯一的,因为它是唯一覆盖默认@@toPrimitive
方法的内部对象,其中默认提示被假定为“字符串”(而不是“数字”)。这样做的原因是为了Date
程序员的方便,默认情况下将实例转换为可读字符串,而不是它们的数值。您可以@@toPrimitive
使用Symbol.toPrimitive
.
以下网格显示了抽象相等运算符 ( ==
) ( source )的强制转换结果:
见也。