揭秘 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

Undefined → NaN

Null → 0

Boolean

  • true → 1
  • false → 0

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

Undefined → false

Null → false

false → false

+-0 → false

"" → 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. 两边类型不同,先将布尔转数字:false → 0,此时 [] == 0
  4. 对象与数字比较,将对象转原始值:ToPrimitive([]) → [].valueOf() → [](非原始)→ [].toString() → ''
  5. '' == 0
  6. 字符串与数字比较,将字符串转数字:Number('') → 0
  7. 0 == 0 → true

四、总结

  • 原始类型 → String: ToString(x) → 直接转换
  • 引用类型 → String: ToPrimitive(x, String) → toString() → valueOf()
  • 原始类型 → Number: ToNumber(x) → 直接转换
  • 引用类型 → Number: ToPrimitive(x, Number) → valueOf() → toString()
  • 任意 → Boolean: ToBoolean(x) → 只有几个值为 false,其余为 true
相关推荐
天外飞雨道沧桑5 分钟前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
柳杉13 分钟前
用 Codex 完成智慧高速 3D 可视化大屏,从 18 张行业设计稿到可交互的运行监测中心
前端·ai编程·数据可视化
review4454323 分钟前
拿到 codex 第一步
前端·agent·vibecoding
鶴哥只手遮天33 分钟前
jQuery 4.0 源码架构与工程实践-第 01 章 · 全景与工程结构:一个 20 岁库的自省
前端·javascript·jquery
徐小夕1 小时前
doc2docx方案,开源!
前端·vue.js·github
徐小夕1 小时前
JitOFD:一款面向信创与档案场景的 Web 端 OFD 编辑器
前端·vue.js·github
孟健2 小时前
Kimi K3.1 Pro 编程实测:3个月27万行代码,每年1.7万预算下如何9分钟修好顽固Bug
前端·开源·ai编程
inksci2 小时前
在线编辑表格,功能强大,json互转
前端·javascript·html
咩咩啃树皮2 小时前
JavaScript 中 this 指向为什么总在变?
开发语言·前端·javascript
彭亚川Allen2 小时前
从零手搓 RAG 之 Embedding 技术
前端·后端·面试