前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库Symbol.toPrimitive 类型转换 hint
JavaScript值与类型

Symbol.toPrimitive 是怎么让对象自己决定转成数字还是字符串的?

对象被拿去做运算、拼字符串时,引擎会先找它的 [Symbol.toPrimitive] 方法,并告诉它这次想要什么:"number"、"string" 还是 "default"。这个方法返回什么原始值,对象就被当成什么,valueOf 和 toString 都不会再被调用。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#值与类型
先看核心答案读代码示例
理解线索

三种 hint 分别来自哪里

  1. "number"一元 +、减乘除、Number(obj)、大小比较
  2. "string"模板字符串、String(obj)、用对象当属性名
  3. "default"二元 + 和 ==,引擎不确定你要数字还是字符串

普通对象收到 default 时按 number 处理,只有 Date 例外,它把 default 当成 string,所以 date + 1 得到的是拼接后的字符串。

核心回答

先记住这个答案

对象参与运算时要先转成原始值。引擎会先查对象上有没有 [Symbol.toPrimitive] 方法,有就调用它,并传入一个 hint 参数表示这次希望得到什么类型:"number"、"string" 或 "default"。方法返回的原始值就是转换结果,valueOf 和 toString 完全不参与。没有这个方法时,才退回老规则:hint 是 string 先调 toString,否则先调 valueOf。

  • 有 Symbol.toPrimitive 时优先调用它
  • hint 有三种:数字、字符串、默认
  • + 与 == 使用 default hint
  • 必须返回原始值,返回对象会抛 TypeError

一个对象,三种场合三种结果

拿一个商品价格对象来试,在方法里把收到的 hint 打印出来,就能看到每种运算到底传了什么。

打印每次收到的 hintJavaScript
const price = {
  [Symbol.toPrimitive](hint) {
    console.log('hint:', hint)
    return hint === 'string' ? '¥99' : 99
  }
}
console.log(+price) // 一元加号要数字
console.log(`${price}`) // 模板字符串要字符串
console.log(price + 1) // 二元加号不确定
console.log(price > 50) // 比较运算要数字
查看输出与解释
hint: number
99
hint: string
¥99
hint: default
100
hint: number
true

前两行好理解。第三行 price + 1 收到的是 default,因为 + 既能做加法又能拼字符串,引擎在转换之前不知道你要哪个,就把决定权交给对象。这里对 default 返回了数字 99,所以结果是 100。如果改成对 default 也返回 '¥99',结果就会变成字符串 '¥991'。

没写 Symbol.toPrimitive 时怎么转

绝大多数对象没有这个方法,这时走的是规范里的 OrdinaryToPrimitive 流程,也就是 valueOf 和 toString 二选一的老办法。hint 是 string 时先调 toString,拿到原始值就用,拿不到再试 valueOf;hint 是 number 或 default 时顺序反过来。

普通对象的 valueOf 返回对象自己,不是原始值,所以最后总会落到 toString,得到 "[object Object]"。这就是 ({}) + 1 结果是 "[object Object]1" 的原因。数组的 toString 会用逗号拼接元素,所以 [1, 2] + '' 是 "1,2"。

为什么推荐用 Symbol.toPrimitive 代替重写 valueOf
重写 valueOf 和 toString 也能控制转换,但你没法知道当前是什么场合,只能被动地被按顺序调用。Symbol.toPrimitive 直接拿到 hint,一个方法里就能把三种场合写清楚,逻辑集中也好测试。

Date 为什么是个例外

Date.prototype 自带了一个 [Symbol.toPrimitive],它把 default 当成 string 处理。这就解释了日期对象一个很迷惑的表现:加法变成拼接,减法却能算时间差。

日期对象的加法和减法JavaScript
const d = new Date(0)
console.log(typeof (d + 1)) // default 按 string 处理,变成拼接
console.log(d - 1) // 减法传 number,拿到时间戳 0 再减 1
console.log(typeof d[Symbol.toPrimitive])
查看输出与解释
string
-1
function

所以算两个日期的间隔可以直接写 end - start,但想给日期加一毫秒不能写 d + 1,要写 d.getTime() + 1 或 +d + 1。

回答前,多想一步

容易答错的地方

以为二元 + 传的是 number
二元 + 和 == 传的都是 default。只有一元 +、减乘除运算,以及 < 这类大小比较才传 number。这个细节决定了 Date 为什么加减表现不一样。
以为有了 Symbol.toPrimitive 还会再调 valueOf
不会。只要这个方法存在,引擎就只调用它。它返回对象的话会直接抛 TypeError: Cannot convert object to primitive value,不会退回去试 valueOf。
以为 hint 就是最终类型
hint 只是一个建议,方法完全可以在 number hint 下返回字符串。比如 +obj 拿到 'abc',引擎会再把它转成数字,得到 NaN。
试着用自己的话回答

面试官还会怎么问?

[] + {} 结果是什么,为什么?

结果是 "[object Object]"。两边都走 default hint,数组和普通对象都没有 Symbol.toPrimitive,valueOf 返回对象自身不算数,于是都调 toString。数组得到空字符串,对象得到 "[object Object]",拼接起来就是后者。

用对象当另一个对象的属性名会怎样?

属性名必须是字符串或 Symbol,所以会用 string hint 转换。普通对象都会变成 "[object Object]",两个不同对象当键会互相覆盖。需要用对象当键时应该换成 Map。

实际项目里什么时候会写 Symbol.toPrimitive?

金额、时长、版本号这类值对象最常用。比如一个 Money 类,数字场合返回分为单位的整数方便计算,字符串场合返回带货币符号的格式化文本,调用方不用每次手动调 format()。

从一道题,走向一组知识

把知识连起来

值与类型

为什么字符串方法都返回新字符串,数组方法却会直接改原数组?

"string" hint 下返回的字符串是全新的原始值,和字符串不可变是同一套值语义。

值与类型

undefined 和 null 到底有什么区别,什么时候该用哪个?

null == undefined 为 true 却不走任何转换,正好和对象参与 == 时调用 Symbol.toPrimitive 形成对比。

参考资料

  • JavaScript:值与类型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 一个对象,三种场合三种结果
  3. 没写 Symbol.toPrimitive 时怎么转
  4. Date 为什么是个例外
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑