揭秘 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
相关推荐
小呆呆66611 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人11 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)11 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan11 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_12 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒12 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技12 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu13 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
weixin_4482902514 小时前
书庐开发实战教学
javascript·css·html
默_笙14 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript