【JavaScript】 隐式类型转换

JavaScript 是弱类型语言,在运算过程中会自动进行类型转换,这就是隐式类型转换(Implicit Coercion)。

一、类型转换的三大核心规则

JavaScript 的隐式转换主要围绕三种类型进行:字符串(String)数字(Number)布尔值(Boolean)

1. 转换为字符串(ToString)

原始值 转换结果
undefined 'undefined'
null 'null'
true 'true'
false 'false'
0 '0'
NaN 'NaN'
对象 调用 toString() 或 valueOf()

2. 转换为数字(ToNumber)

原始值 转换结果
undefined NaN
null 0
true 1
false 0
''(空字符串) 0
'123'(数字字符串) 123
'abc'(非数字字符串) NaN
对象 调用 valueOf() 或 toString()

3. 转换为布尔值(ToBoolean)

假值(Falsy)(转换为 false):

  • undefined

  • null

  • false

  • 0、-0、0n

  • NaN

  • ''(空字符串)

其他所有值都是真值(Truthy),包括:

  • ' '(空格字符串)

  • 'false'(字符串)

  • \[\](空数组)

  • {}(空对象)

二、常见触发隐式转换的场景

1. 算术运算符:+、-、*、/、%

  • + 运算符(特殊!)

    • 任一操作数是字符串 → 字符串拼接

    • 否则 → 数字加法

      javascript 复制代码
      // 字符串拼接
      1 + '2'        // '12'
      'hello' + 1    // 'hello1'
      true + 'abc'   // 'trueabc'
      
      // 数字加法
      1 + true       // 2(true → 1)
      1 + null       // 1(null → 0)
      1 + undefined  // NaN(undefined → NaN)
      1 + []         // '1'([] → '',然后拼接)
      1 + {}         // '1[object Object]'
  • 其他算术运算符(-、*、/、%)

    • 强制转为数字,不进行字符串拼接:

      javascript 复制代码
      '5' - 3        // 2
      '5' * '2'      // 10
      '10' / '2'     // 5
      '10' % '3'     // 1
      'abc' - 1      // NaN
      '5' - null     // 5(null → 0)
      '5' - undefined // NaN

2. 比较运算符

  • 宽松相等 ==

    • 类型相同 → 直接比较

    • 类型不同 → 转为数字再比较

      javascript 复制代码
      1 == '1'           // true('1' → 1)
      true == 1          // true(true → 1)
      false == 0         // true(false → 0)
      null == undefined  // true(特殊规则)
      null == 0          // false(null 只与 undefined 相等)
      NaN == NaN         // false(NaN 不等于任何值)
      '1' == true        // true(都转数字:1 == 1)
      'abc' == true      // false('abc' → NaN,NaN != 1)
      [] == ''           // true([] → '')
      [] == 0            // true([] → '' → 0)
      [1] == 1           // true([1] → '1' → 1)
      [1,2] == '1,2'     // true(数组转字符串)
      {} == '[object Object]' // true
  • 严格相等 ===

    • 不进行类型转换,类型不同直接返回 false:

      javascript 复制代码
      1 === '1'          // false
      true === 1         // false
      null === undefined // false
  • 关系比较 ><>=<=

    • 两个操作数都是字符串 → 字典序比较

    • 否则 → 转为数字比较

      javascript 复制代码
      '2' > '10'         // true(字符串比较:'2' > '10')
      2 > '10'           // false('10' → 10)
      '2' > 10           // false('2' → 2)
      'abc' > 1          // false('abc' → NaN,任何比较都是 false)
      null > 0           // false(null → 0)
      null >= 0          // true(null → 0)

3. 逻辑运算符

  • !(逻辑非)

    • 转为布尔值后取反:

      javascript 复制代码
      !0          // true(0 → false → true)
      !1          // false
      !''         // true
      !'hello'    // false
      !null       // true
      !undefined  // true
      ![]         // false([] 是 truthy)
      !{}         // false({} 是 truthy)
  • &&||

    • 返回操作数本身,不是布尔值:

      javascript 复制代码
      // &&:如果第一个是 falsy,返回第一个,否则返回第二个
      0 && 'hello'      // 0
      1 && 'hello'      // 'hello'
      null && 5         // null
      
      // ||:如果第一个是 truthy,返回第一个,否则返回第二个
      0 || 'hello'      // 'hello'
      1 || 'hello'      // 1
      null || 5         // 5

4. 条件语句(if、while、for、三元运算符)

条件表达式会转为布尔值:

javascript 复制代码
if (0) {}            // false,不执行
if ('') {}           // false,不执行
if (null) {}         // false,不执行
if ([]) {}           // true,执行([] 是 truthy)
if ({}) {}           // true,执行({} 是 truthy)

const result = 0 ? 'yes' : 'no'  // 'no'

5. 一元运算符

javascript 复制代码
+''          // 0
+'123'       // 123
+'abc'       // NaN
+true        // 1
+false       // 0
+null        // 0
+undefined   // NaN
+[]          // 0([] → '' → 0)
+{}          // NaN({} → '[object Object]' → NaN)

-''          // -0
-'123'       // -123
-'abc'       // NaN

三、对象/数组的转换:ToPrimitive

当对象(包括数组、函数等)参与运算时,会先执行 ToPrimitive 操作,将其转换为原始值。

转换流程:

  • 优先调用 Symbol.toPrimitive(如果存在)

  • 否则根据上下文提示(hint)调用 valueOf() 或 toString()

    javascript 复制代码
    const obj = {
        valueOf() { return 10; },
        toString() { return 'hello'; }
    };
    
    console.log(obj + 5)     // 15(使用 valueOf)
    console.log(String(obj)) // 'hello'(使用 toString,hint 是 string)
    console.log(+obj)        // 10(使用 valueOf,hint 是 number)

数组的特殊行为:

javascript 复制代码
[] + []       // ''(两个数组都转空字符串)
[] + {}       // '[object Object]'
{} + []       // 0(在浏览器中,{} 被解析为代码块,相当于 +[])
[1,2] + [3,4] // '1,23,4'(数组转字符串:'1,2' + '3,4')
[1] + 1       // '11'([1] → '1' → '1' + 1)
[1] - 1       // 0([1] → '1' → 1 - 1)

自定义 ToPrimitive

javascript 复制代码
const obj = {
    [Symbol.toPrimitive](hint) {
        if (hint === 'number') return 10;
        if (hint === 'string') return 'hello';
        return null;
    }
};

console.log(obj + 5)     // 15(number hint)
console.log(`${obj}`)    // 'hello'(string hint)
console.log(obj + '')    // 'null'(default hint)

四、特殊陷阱和难点

  1. + 运算符的歧义

    javascript 复制代码
    // 一元 + 和二元 + 不同
    +[]         // 0(一元 + 转数字)
    [] + []     // ''(二元 + 字符串拼接)
    
    // 注意区分
    1 + 2 + '3'   // '33'(从左到右:3 + '3' → '33')
    '1' + 2 + 3   // '123'(从左到右:'12' + 3 → '123')
  2. nullundefined 的特殊性

    javascript 复制代码
    null == undefined   // true(特殊规则)
    null === undefined  // false
    null == 0           // false(null 只与 undefined 相等)
    undefined == 0      // false
    Number(null)        // 0
    Number(undefined)   // NaN
  3. 数组比较的陷阱

    javascript 复制代码
    [] == ![]    // true(![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0)
    [] == []     // false(引用比较,不是同一个对象)
    [1] == [1]   // false(引用比较)
    [1] == 1     // true([1] → '1' → 1)
  4. 布尔值比较的陷阱

    javascript 复制代码
    if (' ') {}           // true(空格字符串是 truthy)
    if ('false') {}       // true(字符串 'false' 是 truthy)
    if (0) {}             // false
    if (new Boolean(false)) {} // true(对象总是 truthy)
  5. Symbol 的特殊性

    javascript 复制代码
    const sym = Symbol('id');
    sym + 1        // TypeError(不能隐式转换 Symbol)
    String(sym)    // 'Symbol(id)'(可以显式转换)

五、如何避免隐式转换的坑

  1. 使用严格相等 === !==

    javascript 复制代码
    // 避免
    if (value == 0) {}
    
    // 推荐
    if (value === 0) {}
    if (value === null || value === undefined) {}
    2. 显式类型转换
    javascript
    // 不推荐
    '5' - 0     // 5
    
    // 推荐
    Number('5') // 5
    parseInt('5', 10) // 5
    + '5'       // 5(一元 + 是显式转数字)
    
    // 不推荐
    5 + ''      // '5'
    
    // 推荐
    String(5)   // '5'
    `${5}`      // '5'
  2. 使用 Number.isNaN() 检测 NaN

    javascript 复制代码
    isNaN('abc')   // true(先转数字)
    Number.isNaN('abc') // false(不转换,直接判断)
    Number.isNaN(NaN)    // true
  3. 使用 Object.is() 替代 ===

    javascript 复制代码
    NaN == NaN // false
    NaN === NaN   // false
    Object.is(NaN, NaN)  // true
    +0 === -0     // true
    Object.is(+0, -0)    // false

六、常见面试题汇总

javascript 复制代码
console.log([] == ![])  // true (![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0)
console.log([] == 0)    // true
console.log([] == '')   // true
console.log({} == 0)    // false({} → '[object Object]' → NaN)
console.log({} == '[object Object]') // true

console.log('5' - 3)    // 2
console.log('5' + 3)    // '53'
console.log('5' - '3')  // 2
console.log('5' + '3')  // '53'

console.log(true + false)     // 1
console.log(true + true)      // 2
console.log([] + [])          // ''
console.log([] + {})          // '[object Object]'
console.log({} + [])          // 0(浏览器)或 '[object Object]'

// 4. 输出什么?
console.log(1 < 2 < 3)    // true(1 < 2 → true → 1 < 3 → true)
console.log(3 > 2 > 1)    // false(3 > 2 → true → 1 > 1 → false)

// 5. 输出什么?
const a = {
    valueOf() { return 1; },
    toString() { return '2'; }
};
console.log(a + 1)    // 2(valueOf 优先)
console.log(String(a)) // '2'(toString 优先)