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

一、类型转换分类

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

类别 说明
显式类型转换 主动调用 String()Number()Boolean() 等进行转换
隐式类型转换 JS 引擎在运算、比较等场景下自动进行的类型转换

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

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

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

js 复制代码
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)

js 复制代码
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)

js 复制代码
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 引用类型转字符串:

js 复制代码
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 引用类型转数字:

js 复制代码
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 == 运算符 ------ 经典案例分析

js 复制代码
[] == ![]

分析过程:

  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
相关推荐
最爱睡觉睡觉睡觉1 小时前
Flutter ThemeData 主题系统
前端·app
最爱睡觉睡觉睡觉1 小时前
pub.dev 常用包 vs npm 生态对照
前端·app
先吃饱再说1 小时前
从三行代码理解前端的“三权分立”:HTML、CSS、JS 各司其职
前端
biubiubiu_LYQ1 小时前
入门开发者基础篇之CSS浮动布局:一文吃透浮动底层逻辑
前端·css
最爱睡觉睡觉睡觉1 小时前
React Hooks → Flutter 等价写法
前端·app
最爱睡觉睡觉睡觉1 小时前
CSS → Flutter 对照手册
android·前端
xiaofeichaichai1 小时前
Service Worker、PWA 与 Web Worker — 离线缓存与主线程算力分离
前端·缓存
JustHappy1 小时前
古法编程秘籍(四):函数究竟是什么?把函数最重要的能力一次讲清楚
前端·后端·面试
OpenTiny社区1 小时前
一行命令添加 AI 对话入口!TinyRobot 也太省事了~
前端·vue.js·ai编程