揭秘 JS 类型转换:ToPrimitive 机制的神秘面纱

一、类型转换分类

JavaScript 是一门弱类型语言,变量在声明时不需要指定类型,且在运行过程中会发生各种类型转换。类型转换分为显式类型转换和隐式类型转换两大类,理解类型转换是掌握 JS 底层机制的关键一步。

类别

说明

显式类型转换

主动调用 String()Number()Boolean() 等进行转换

隐式类型转换

JS 引擎在运算、比较等场景下自动进行的类型转换


二、原始类型之间的显式转换

2.1 String(x) —— 转换为字符串

底层调用 ToString(x)(注意:是大写 T,不是原型上的 toString)。

console.log(String());            // ''(空字符串)
console.log(String(undefined));   // 'undefined'
console.log(String(null));        // 'null'
console.log(String(true));        // 'true'
console.log(String(false));       // 'false'
console.log(String(-0));          // '0'(-0 与 0 相等,输出 '0')
console.log(String(NaN));         // 'NaN'
console.log(String(Infinity));    // 'Infinity'(数学中的无穷大)

ToString (x)方法:

完整转换规则

无值""

Undefined"undefined"

Null"null"

Boolean

  • true"true"

  • false"false"

Number

  • 常规数字:直接转字符串(123"123"

  • NaN"NaN"

  • Infinity"Infinity"

String → 原值(不转换)

Symbol → 报错(TypeError)

Object → 调用ToPrimitive(x , String)

2.2 Number(X) —— 转数字

底层调用 ToNumber(x)

console.log(Number()); //0
console.log(Number(undefined)); // NaN,无法处理
console.log(Number(null)); //0
console.log(Number(true)); //1
console.log(Number('123')); //123
console.log(Number('-123')); //-123
console.log(Number('1.123')); //1.123
console.log(Number('00123')); //123
console.log(Number('00123a')); // NaN,无法处理

ToNumber(x)方法:

无值0

UndefinedNaN

Null0

Boolean

  • true1

  • false0

Number → 原值

String

  • 空字符串 ""0

  • 纯数字字符串 → 对应数字("123"→123)

  • 含非数字字符 → NaN

Symbol → 报错

Object → 调用ToPrimitive(x , Number)

2.3 Boolean(X) —— 转布尔

底层调用 ToBoolean(x)

console.log(Boolean()); //false
console.log(Boolean(undefined)); //false
console.log(Boolean(null)); //false
console.log(Boolean(0)); //false
console.log(Boolean(-0)); //false
console.log(Boolean(NaN)); //false
console.log(Boolean('')); //false   字符串转布尔 只有空字符串是false
console.log(Boolean(' ')); //true

ToBoolean(x)方法:

无值false

Undefinedfalse

Nullfalse

falsefalse

+-0false

""false

其余均为true

注意:

ToString(x) ToNumber(x) ToBoolean(x)方法是 JS 引擎在需要转字符串 / 数字 / 布尔值时,自动执行的转换逻辑,我们无法直接调用

三、引用类型的显式转换

3.1 引用类型转字符串:

console.log(String({a: 1}));  // '[object Object]'
console.log(String([]));      // ''(空字符串)

引用类型转字符串的底层路径:String(x)ToString(x)(判断为Object类型) → ToPrimitive(x, String)

ToPrimitive(x, String) 的执行流程:

  1. 调用 x.toString(),如果得到一个原始值,则直接返回

  2. 否则调用 x.valueOf(),如果得到一个原始值,则直接返回

  3. 否则报错

{a: 1} 为例:

  • {a: 1}.toString() 返回 '[object Object]',得到了原始值,直接返回

[] 为例:

  • [].toString() 返回 ''(数组内部元素以逗号拼接,空数组即空字符串),得到了原始值,直接返回

3.2 引用类型转数字:

console.log(Number({}));  // NaN
console.log(Number([]));  // 0

引用类型转数字的底层路径:Number(x)ToNumber(x)(判断为Object类型) → ToPrimitive(x, Number)

{} 为例的转换过程:

  1. ToPrimitive({}, Number)

  2. 调用 {}.valueOf() → 返回 {} 本身,不是原始值

  3. 调用 {}.toString() → 返回 '[object Object]'

  4. 然后 Number('[object Object]')NaN

[] 为例的转换过程:

  1. ToPrimitive([], Number)

  2. 调用 [].valueOf() → 不是原始值

  3. 调用 [].toString() → 返回 ''(空字符串)

  4. 然后 Number('')0

注意: ToPrimitive 的第二个参数是 String 时,优先调用 toString();是 Number 时,优先调用 valueOf()valueOf() 只能将包装类的对象转成原始值

3.3 toString() 方法补充

每种原型中都有一个 toString 方法:

调用

返回值

来源

{}.toString()

'[object Object]'

对象原型

[].toString()

数组元素以逗号拼接的字符串(空数组为 ''

数组原型

'xx'.toString()

'xx'

其他

三、隐式类型转换

隐式类型转换绝大多数朝数字转,发生在以下场景:

  • 四则运算: + - * / %

  • 判断语句: if while == >= <= != > <

  • + 作为一元运算符: 朝数字转,如 +'1'1


3.1 + 作为二元运算符

规则:
x + y

  1. lprim = ToPrimitive(x)

  2. 令 rprim = ToPrimitive(y)

  3. lprim + rprim

  4. 如果 lprim 或 rprim 中有一个是字符串,则将另一个也转成字符串进行拼接

  5. 否则全部转成 Number 进行相加

示例:[] + [] 分析

[] + []

  1. lval + rval

  2. 令 lprim = ToPrimitive([]) → [].valueOf() 返回[](非原始) → [].toString() 返回 '[object Object]'

  3. 令 rprim = ToPrimitive([]) → [].valueOf() 返回[](非原始) → [].toString() 返回 ''

    '' +'' (两个原始值都是字符串,字符串拼接)

  4. 结果: ''

示例:{} + [] 分析

当 {} 在语句开头时,浏览器引擎将其解析为代码块而非对象字面量,但如果正确解析:

{} + []

  1. lval + rval

  2. 令 lprim = ToPrimitive({}) → {}.valueOf() 返回{}(非原始) → {}.toString() 返回'[object Object]'

  3. 令 rprim = ToPrimitive([]) → [].valueOf() 返回[](非原始) → [].toString() 返回 ''

    '' + '' (两个原始值都是字符串,字符串拼接)

  4. 结果: '[object Object]'

3.2 == 运算符 —— 经典案例分析

[] == ![]

分析过程:

  1. ! 字符优先级比==高,先![] → 所有引用类型转布尔都是 true,所以 !true = false

  2. [] == false

  3. 两边类型不同,先将布尔转数字:false0,此时 [] == 0

  4. 对象与数字比较,将对象转原始值:ToPrimitive([])[].valueOf()[](非原始)→ [].toString()''

  5. '' == 0

  6. 字符串与数字比较,将字符串转数字:Number('')0

  7. 0 == 0true

四、总结

  • 原始类型 → String: ToString(x) → 直接转换

  • 引用类型 → String: ToPrimitive(x, String)toString()valueOf()

  • 原始类型 → Number: ToNumber(x) → 直接转换

  • 引用类型 → Number: ToPrimitive(x, Number)valueOf()toString()

  • 任意 → Boolean: ToBoolean(x) → 只有几个值为 false,其余为 true

聊天