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)的强制结果:

见也。
window.console.log (x);还是alert (x);?