前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库对象转字符串 valueOf toString 顺序
JavaScript值与类型

对象转字符串时,先调 valueOf 还是先调 toString?

看场景。String(obj) 和模板字符串要的是字符串,先调 toString;+obj、obj * 2 要的是数字,先调 valueOf;obj + '' 和 == 属于「没说要什么」,普通对象也是先调 valueOf。想完全自己控制,就定义 Symbol.toPrimitive。

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

对象转原始值看 hint

  1. stringString(obj)、模板字符串、对象当属性名时,顺序是 toString 再 valueOf
  2. number+obj、Number(obj)、obj * 2、obj > 1 时,顺序是 valueOf 再 toString
  3. default二元 + 和 == 时使用,普通对象按 number 处理

Date 是例外。它自己的 Symbol.toPrimitive 把 default 当成 string 处理,所以 date + 1 得到的是日期字符串拼上 1,而不是时间戳加 1。

核心回答

先记住这个答案

对象参与运算时,引擎会先把它转成原始值,这一步叫 ToPrimitive,会带一个 hint 说明「这次想要什么类型」。hint 是 string 时,先调 toString,拿不到原始值再调 valueOf;hint 是 number 或 default 时,顺序反过来,先 valueOf 再 toString。所以并不存在一个固定的「先谁后谁」,要看是哪种运算触发的转换。如果对象上定义了 Symbol.toPrimitive,引擎只调它,把 hint 当参数传进去,另外两个方法都不会碰。

  • 转字符串通常先试 toString
  • 普通对象的默认提示先试 valueOf
  • Symbol.toPrimitive 优先参与转换
  • 两个方法都返回对象时抛 TypeError

同一个对象,四种写法四种顺序

最直观的办法是给对象同时写上 valueOf 和 toString,每个方法被调用时打一行日志,然后换着花样去用它。

打印调用顺序JavaScript
const obj = {
  valueOf() { console.log('调用 valueOf'); return 42 },
  toString() { console.log('调用 toString'); return 'obj' }
}
console.log('String(obj):')
console.log(String(obj)) // hint 是 string
console.log('模板字符串:')
console.log(`${obj}`) // hint 是 string
console.log("obj + '':")
console.log(obj + '') // hint 是 default
console.log('+obj:')
console.log(+obj) // hint 是 number
查看输出与解释
String(obj):
调用 toString
obj
模板字符串:
调用 toString
obj
obj + '':
调用 valueOf
42
+obj:
调用 valueOf
42

最容易答错的是第三个。很多人觉得「跟空字符串相加,肯定是转字符串」,但二元 + 在转换对象时还不知道另一边是不是字符串,所以传的是 default。普通对象把 default 当 number 处理,先调 valueOf 拿到 42,然后才发现另一边是字符串,再把 42 拼成 '42'。 平时感觉不到这一点,是因为普通对象继承来的 valueOf 返回对象本身,不是原始值,引擎只好接着调 toString,最后得到 '[object Object]'。只有自己写了 valueOf,顺序的差别才会露出来。

用 Symbol.toPrimitive 自定义转换

valueOf 和 toString 是「被动」的:你只能决定返回什么,没法知道这次是谁在要。Symbol.toPrimitive 会直接收到 hint,可以按场景返回不同的值。

一个价格对象JavaScript
const price = {
  amount: 99,
  [Symbol.toPrimitive](hint) {
    console.log('hint =', hint)
    if (hint === 'string') return `¥${this.amount}`
    return this.amount // number 和 default 都返回数字
  }
}
console.log(`${price}`) // ¥99
console.log(price * 2) // 198
console.log(price + 1) // 100
console.log(price == 99) // true
查看输出与解释
hint = string
¥99
hint = number
198
hint = default
100
hint = default
true

展示的时候拿到带货币符号的字符串,计算的时候拿到数字,同一个对象两种用法都顺手。== 和二元 + 一样传 default,这也印证了上一节的结论。 这个方法必须返回原始值,返回对象会直接抛 TypeError,不会再回退到 valueOf 或 toString。

返回值不是原始值时怎么回退

valueOf 和 toString 各自被调用时,只要返回的是原始值就采用;返回对象就当它没给结果,接着试下一个。两个都失败才报错。

回退和报错JavaScript
// toString 返回对象,不算数,接着试 valueOf
const a = { toString() { return {} }, valueOf() { return 'from valueOf' } }
console.log(String(a))

// 两个都返回对象,直接报错
const b = { toString() { return {} }, valueOf() { return {} } }
try { String(b) } catch (e) { console.log(e.name) }

// 没有原型的对象,两个方法都找不到
const c = Object.create(null)
try { `${c}` } catch (e) { console.log(e.name) }
查看输出与解释
from valueOf
TypeError
TypeError

第三种在项目里真会遇到。Object.create(null) 常被拿来做纯字典,它没有继承任何方法,一旦被拼进日志字符串就会抛错,所以打印这类对象要用 JSON.stringify。

Date 为什么不一样
Date.prototype 上自带一个 Symbol.toPrimitive,收到 default 时按 string 处理。所以 date + 1 得到类似 'Tue Sep 29 2026 ... 1' 的字符串,而 date - 0 和 +date 走 number,得到时间戳。想拿时间戳时,写 date.getTime() 最清楚。
回答前,多想一步

容易答错的地方

说「对象转换总是先 valueOf 再 toString」
这只对 number 和 default 成立。String(obj)、模板字符串、把对象当属性名(map[obj])都是 string hint,先调 toString。回答时要把 hint 说出来,否则面试官一问 String(obj) 就露馅。
以为 obj + '' 和 String(obj) 等价
两者只在对象没有自定义 valueOf 时结果一样。前者先调 valueOf,后者先调 toString。上面的例子里一个得到 '42',一个得到 'obj'。
以为 Symbol.toPrimitive 返回对象会回退
不会。Symbol.toPrimitive 存在就只用它,返回对象直接抛 TypeError。回退逻辑只发生在没有定义它、走 valueOf 和 toString 的时候。
试着用自己的话回答

面试官还会怎么问?

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

结果是 '[object Object]'。二元 + 对两边都用 default 转换:数组的 valueOf 返回数组本身,不是原始值,于是调 toString 得到空字符串;对象同理得到 '[object Object]'。只要有一边是字符串,+ 就做拼接,所以结果是两者相连。

怎么让 a == 1 && a == 2 && a == 3 成立?

利用 == 会对对象做 default 转换、每次比较都重新调用转换方法这一点。给 a 写一个 valueOf,内部维护一个计数器,每调一次加 1 并返回。三次比较分别拿到 1、2、3,表达式就成立。换成 === 就不行,因为严格相等不做类型转换。

自定义 toString 会影响 console.log(obj) 吗?

一般不会。console.log 打印对象时由控制台自己做格式化展示,不会走 ToPrimitive。只有把对象拼进字符串,比如 console.log('值:' + obj) 或模板字符串,才会调用你的 toString。

从一道题,走向一组知识

把知识连起来

值与类型

a = b === c 到底先算哪一步?优先级会引出哪些 bug?

obj == 1 这类比较会触发对象的 default 转换,和运算符优先级一起决定表达式最后怎么求值。

值与类型

一元加号 +x 和 Number(x) 有什么区别?

一元 +obj 就是以 number hint 转换对象,会先调 valueOf,两题的规则是连着的。

参考资料

  • JavaScript:值与类型参考资料

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

本题目录
  1. 先记住这个答案
  2. 同一个对象,四种写法四种顺序
  3. 用 Symbol.toPrimitive 自定义转换
  4. 返回值不是原始值时怎么回退
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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