跳到主要内容

隐式类型转换

· 阅读需 8 分钟

在网上看到一道很有意思的题目: [] + {} = '[object Object]',这道题的核心在于JavaScript 中的隐式类型转换

隐式类型转换的场景

在 JavaScript 中,隐式类型转换是指在某些操作中自动将值从一种类型转换成另一种类型的过程。以下列举一些常见的隐式转换场景

1 字符串连接

使用加号 (+) 运算符连接非字符串时,涉及到的值会被转换成字符串。

let a = "The answer is " + 42; // "The answer is 42"
let b = 42 + " is the answer"; // "42 is the answer"

2 执行数学运算

当使用除了加号 (+) 之外的算数运算符时,涉及到的字符串值将转换为数字。

let a = '5' * '4'; // 20 (字符串被转换为数值)
let b = '5' - 2; // 3
let c = '10' / '2';// 5

3 条件判断

在像 ifwhile等语句的条件判断中,值会被自动转换成布尔值。

if ('hello') { // 'hello' 被隐式转换为 true
console.log('This string is considered true');
}

4 == 比较

使用 == 来比较对象和数字时,对象先被转换为一个原始值,通常是数字。

let a = { valueOf: () => 42 };
let b = a == 42; // true

比较对象和字符串时,一样会发生隐式类型转换。

let a = { toString: () => "3" };
let b = a == 3; // true

5 逻辑操作

使用逻辑或 (||) 和逻辑与 (&&) 操作符时,操作数会转换为布尔值进行判断。

let a = 'cat' && 'dog'; // 'dog' - 'cat' 转换为 true,返回最后一个真值
let b = 'cat' || 'dog'; // 'cat' - 'cat' 转换为 true,返回第一个真值

逻辑非运算符 (!) 会将操作数转换为布尔值的相反值。

let truthyValue = "hello";
let falsyResult = !truthyValue; // !'hello' 转换为 false

隐式类型转换规则

JavaScript中的隐式类型转换通常遵循一些基本规则,这些规则确定了当一个操作涉及不同类型的值时,如何对它们进行转换。这里是一些核心规则和优先级:

1 字符串连接优先级规则:

JavaScript 的加号 (+) 是一个多态的操作符,它同时表示字符串连接和数字加法。其行为取决于操作数类型:

  • 如果两个操作数都是数值或者能够转换为数值(或者通过调用 valueOf() 方法得到数字),加号执行数学加法。

  • 如果操作中有任何一个操作数是字符串(或者通过调用 toString() 方法能够得到字符串),那么其他操作数都会转换为字符串并进行连接。

  • 字符串连接优先

2 数值运算优先级规则

  • 如果操作数不是字符串,对于减法 (-)、乘法 (*)、除法 (/) 和取余 (%) 运算,非数值会转换为数值(通过调用 valueOf(),如果不行再调用 toString(),然后转换为数值)。
  • 对于一元加号 (+) 和一元减号 (-) 运算符(例如 +something-something),操作数会转换为数值。

3 逻辑运算优先级规则:

  • 对于逻辑运算如 &&||,操作数会转换为布尔值来评估逻辑表达式。
  • || 返回第一个“真值”(truthy value),如果没有则返回最后一个操作数。
  • && 返回第一个“假值”(falsy value),如果所有都是真值,则返回最后一个操作数。

4 比较运算优先级规则:

  • 在使用 ==!= 进行相等性比较时,如果一个操作数是数字,则另一个操作数会尝试转换为数字。
  • 如果一个操作数是布尔值,在比较之前,布尔值会先被转换为数字(true 转换为 1false 转换为 0)。
  • 如果一个操作数是对象,且另一个是字符串、数字或符号时,对象会通过 valueOftoString 方法转换为原始值进行比较。

5 Boolean Context规则:

  • 在期望布尔值的上下文(如 if 语句)中,值会被转换为布尔值。这包括规则如:nullundefined+0-0NaN 和空字符串 ('') 被转换为 false;其他所有值(包括 "0"'false')被转换为 true

在这些规则之外,还有一个概念叫做“假值”(falsy)和“真值”(truthy):

  • 假值 是在布尔上下文中会被转换为 false 的值,包括:+0-0nullfalseNaNundefined 和空字符串 ''
  • 真值 是在布尔上下文中会被转换为 true 的值,即除了假值之外的所有值。

现在我们来分析这道题

[] + {} = '[object Object]'
// 当 + 运算符用于数组和对象时,JavaScript 会尝试将数组和对象转换为它们的原始值,
// 如果操作中有任何一个操作数是字符串(或者通过调用 `toString()` 方法能够得到字符串),那么就会进行优先进行字符串的拼接
// [] 在转换为原始值时会先调用 Array.prototype.toString 方法,该方法会隐式调用 Array.prototype.join 方法,将数组元素连接成字符串。对于空数组,结果就是空字符串。
// 在将 {} 转为原始类型的过程中,JavaScript 内部会调用 Object.prototype.toString 方法作为转换机制的一部分,返回了对象的字符串形式。
// 因此,最终 [] 被转换为 ''(空字符串),{} 被转换为 '[object Object]',它们通过 + 运算符连接在一起就变成了 '' + '[object Object]',结果是 '[object Object]'。

Object.is()===== 的区别

  • == 会触发隐式类型转换,其他两个不会

  • 其他两个以下两个特殊场景也存在区别:

    • NaN
    Object.is(NaN, NaN) // true
    NaN === NaN // false
    • +0 -0
    Object.is(+0, -0) // false
    +0 === -0 // true
    // Object.is 能够区别正负

    总结:除了特殊情况,就用 === 即可。求稳的话就用 is