前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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.toStringTag 自定义 Object.prototype.toString
JavaScript对象与原型

怎么用 Symbol.toStringTag 改变 Object.prototype.toString 的输出?

Object.prototype.toString.call(obj) 返回 [object 标签]。如果 obj 上能读到一个字符串类型的 Symbol.toStringTag 属性,标签就用它的值。所以给类加一个 get [Symbol.toStringTag]() { return 'Validator' },结果就变成 [object Validator]。

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

`toString` 怎么决定标签

  1. 内置标签数组、函数、Date、RegExp、Error 等按内部类型给出默认标签,其他对象默认是 Object
  2. Symbol.toStringTag能读到且是字符串,就覆盖默认标签,原型上的也算
  3. null 和 undefined直接返回 [object Null] 和 [object Undefined],不走查找

这个属性只影响 Object.prototype.toString,不影响 typeof、instanceof,也不影响你自己重写过的 toString 方法。

核心回答

先记住这个答案

Object.prototype.toString 的返回值格式固定是 [object 标签]。标签的来源是这样的:先根据对象的内部类型得到一个默认标签,比如数组是 Array,普通对象是 Object;然后去读对象的 Symbol.toStringTag 属性,如果读到的是字符串,就用它替换默认标签。读的时候会走原型链,所以一般在类里定义一个 getter:get [Symbol.toStringTag]() { return 'Validator' },这个类的所有实例调用 toString 都会得到 [object Validator]。

  • Symbol.toStringTag 可改类型标签
  • 通常在类里写成原型上的 getter
  • Map、Promise 的标签就是靠这个属性来的
  • 标签可以伪造,数组都能被改成别的标签

自定义标签,以及它能改到什么程度

下面同时演示自定义类、内置对象,以及一个故意伪造的数组。

读取不同对象的标签JavaScript
const toString = Object.prototype.toString
class Validator {
  get [Symbol.toStringTag]() { return 'Validator' } // 定义在原型上
}
const v = new Validator()
console.log(toString.call(v))
console.log(String(v)) // 实例继承的 toString 就是这一个
console.log(toString.call(new Map()), toString.call(Promise.resolve()))

const arr = [1]
arr[Symbol.toStringTag] = 'NotArray'
console.log(toString.call(arr), Array.isArray(arr))
console.log(toString.call({ [Symbol.toStringTag]: 123 })) // 不是字符串,忽略
console.log(toString.call(null))
查看输出与解释
[object Validator]
[object Validator]
[object Map] [object Promise]
[object NotArray] true
[object Object]
[object Null]

String(v) 也变了,因为 v 没有自己的 toString,最终调用的就是 Object.prototype.toString,模板字符串 ${v} 同理。 Map 和 Promise 并没有专门的内部标签,它们的标签来自 Map.prototype[Symbol.toStringTag] 这个内置属性,值就是字符串 'Map'。JSON、Math、Symbol、ArrayBuffer、生成器对象也都是这样。在 ES6 之前,这些对象要么不存在,要么只能返回 [object Object]。 数组那一行最能说明问题:一个真正的数组,只要加上这个属性,toString 就会报出假标签,而 Array.isArray 不受影响。标签值不是字符串时会被忽略,退回默认标签。

为什么写成原型上的 getter

也可以在构造函数里写 this[Symbol.toStringTag] = 'Validator',结果一样,但每个实例都多一个自身属性。写成 getter 放在原型上只有一份,而且它不可枚举,Object.keys、JSON.stringify、展开运算符都不会碰到它,不会污染数据。

还有一个细节:用 getter 可以根据状态返回不同标签,但这样做会让调试更困惑,实际项目里一般返回固定字符串。

这对类型判断意味着什么

很多工具库用 Object.prototype.toString.call(x) === '[object Array]' 判断类型,因为它能跨 iframe,还能区分 null。有了 Symbol.toStringTag 之后,这种判断对普通对象就不再可靠了,任何对象都能把自己的标签写成 Date。判断数组要用 Array.isArray,判断其他内置类型也最好用专门的方法,或者检查内部行为,比如调用 Date.prototype.getTime.call(x) 看会不会抛错。

回答前,多想一步

容易答错的地方

以为改了标签会影响 typeof 或 instanceof
不会。typeof v 还是 'object',v instanceof Validator 还是 true。instanceof 能被定制的是另一个内置符号 Symbol.hasInstance。
以为自定义了 toString 方法也会用到这个标签
Symbol.toStringTag 只被 Object.prototype.toString 读取。如果类里自己写了 toString() 方法,String(v) 调的就是你写的那个,标签完全不参与。但 Object.prototype.toString.call(v) 仍然会用标签。
以为数组、函数的标签改不了
可以改。默认标签只是兜底,只要对象上能读到字符串类型的 Symbol.toStringTag,就以它为准,数组也不例外。
试着用自己的话回答

面试官还会怎么问?

写成 static [Symbol.toStringTag] 行不行?

不行,至少达不到目的。static 会把属性挂到类本身上,结果是 Object.prototype.toString.call(Validator) 返回 [object Validator],而实例的原型链上找不到它,实例还是 [object Object]。要影响实例,必须定义在原型上,也就是不加 static 的 getter。

Object.prototype.toString.call(1) 是什么?

[object Number]。原始值会先被包装成对应的对象,Number.prototype 上没有 Symbol.toStringTag,用的是内部类型给的默认标签。

为什么 class 实例默认是 [object Object] 而不是类名?

因为默认标签只看内部类型,普通的类实例在内部就是普通对象,规范不会自动读取类名。想让它显示类名,就得自己定义 Symbol.toStringTag。

从一道题,走向一组知识

把知识连起来

对象与原型

用 WeakMap 怎么模拟 class 的私有属性?和 # 私有字段比各有什么优劣?

WeakMap 实例的 [object WeakMap] 标签就来自 WeakMap.prototype[Symbol.toStringTag],可以顺手验证本题的规则。

值与类型

为什么 String([]) 是空字符串,而 String([1,2]) 是 '1,2'?

数组转字符串走的是 Array.prototype.toString,它覆盖了 Object.prototype.toString,所以得到 '1,2' 而不是 [object Array]。

参考资料

  • JavaScript:对象与原型参考资料

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

本题目录
  1. 先记住这个答案
  2. 自定义标签,以及它能改到什么程度
  3. 为什么写成原型上的 getter
  4. 这对类型判断意味着什么
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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