前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库原始类型包装对象方法
JavaScript值与类型

字符串是原始类型,为什么还能调用 toUpperCase 这样的方法?

原始值本身不是对象,没有属性也没有方法。执行 'hello'.toUpperCase() 时,引擎临时创建一个 String 包装对象,顺着它找到 String.prototype.toUpperCase 调用,调用完这个临时对象就被丢掉了。这个过程叫自动装箱。

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

原始值和包装对象的关系

  1. 自动装箱读 s.length、调 s.slice() 时,引擎像 Object(s) 那样临时包一层对象
  2. 原型方法包装对象的原型是 String.prototype、Number.prototype,方法都在那里
  3. 拆箱包装对象参与运算时调 valueOf 拿回原始值,new String('a') == 'a' 因此成立

null 和 undefined 没有对应的包装类型,所以 null.toString() 会抛 TypeError。这就是可选链 ?. 最常用的场景。

核心回答

先记住这个答案

字符串、数字、布尔这些原始值本身不是对象,身上没有任何属性。当你写 'hello'.toUpperCase() 时,引擎发现要在原始值上访问属性,就先用对应的构造函数把它临时包成一个对象,也就是 String 包装对象,然后从这个对象的原型 String.prototype 上找到 toUpperCase 调用,最后把临时对象丢掉。这就是自动装箱。所以原始值「看起来」有方法,实际上方法都借自包装类型的原型,而且每次访问都是一个新的临时对象。

  • 访问原始值的属性时,会临时包装成对象
  • 方法在 String.prototype 上,不在值本身
  • 给原始值加属性不会报错,但下一行就读不到了
  • 别用 new String 创建包装对象

装箱的过程和它留下的痕迹

临时对象用完就扔,这一点可以从「给字符串加属性」的结果看出来。

属性加上去就没了JavaScript
const s = 'hello'
console.log(s.toUpperCase(), s.length) // 临时包装成 String 对象再调用

s.tag = 'x' // 属性加在了临时对象上,随后被丢弃
console.log(s.tag) // undefined

// 方法并不在 s 身上,而是在 String.prototype 上
console.log(s.toUpperCase === String.prototype.toUpperCase)
console.log(typeof s, typeof Object(s))
查看输出与解释
HELLO 5
undefined
true
string object

s.tag = 'x' 这一行,引擎创建了一个临时的 String 对象,把 tag 挂在它上面,然后这个对象就没人引用了。下一行读 s.tag 时又创建了一个新的临时对象,上面当然没有 tag。这段代码在非严格模式下静默失败;在严格模式下,给原始值设置属性会直接抛 TypeError,更容易发现问题。 Object(s) 会显式地做一次装箱,得到的是真正的对象,这也说明 typeof 的结果取决于你手里拿的是原始值还是包装对象。

引擎真的每次都创建对象吗
规范是这样描述行为的,但 V8 这类引擎会做优化,调用 'abc'.length 时并不会真的分配一个对象。所以不用担心在循环里调用字符串方法有额外开销,这里讲的是语义,不是实现。

手动创建包装对象的坑

String、Number、Boolean 也可以用 new 调用,得到一个长期存在的包装对象。它和原始值在很多地方表现不同,实际项目里几乎没有理由这样写。

new String 和原始值对比JavaScript
const a = new String('hi')
const b = 'hi'
console.log(typeof a, typeof b) // object string
console.log(a == b, a === b) // 宽松相等会拆箱,严格相等不会
console.log(new String('hi') === new String('hi')) // 两个不同对象

const flag = new Boolean(false)
if (flag) console.log('对象永远是真值')
console.log(flag.valueOf()) // 拆箱拿回原始值
查看输出与解释
object string
true false
false
对象永远是真值
false

最危险的是最后一段。new Boolean(false) 是一个对象,所有对象在条件判断里都是真值,所以 if 会进去,逻辑完全反了。想把值转成布尔,写 Boolean(x) 或 !!x,不带 new。

Symbol 和 BigInt 不能 new
ES6 之后新增的两个原始类型干脆禁止了 new,new Symbol() 和 new BigInt(1) 都会抛 TypeError,就是为了不让大家再踩上面这些坑。它们依然能自动装箱,所以 Symbol('a').description、10n.toString() 都能正常用。
回答前,多想一步

容易答错的地方

说「字符串是对象,所以有方法」
字符串是原始值,typeof 返回 string。它能调用方法靠的是临时装箱,不是因为它本身是对象。这个区别解释了为什么字符串不可变,也解释了为什么给它加属性会丢。
以为 null 和 undefined 也会装箱
这两个值没有对应的构造函数和原型,访问它们的任何属性都会抛 TypeError,比如常见的 Cannot read properties of undefined。所以接口数据不确定时要写 data?.name,而不是指望引擎兜底。
用 instanceof 判断字符串
'hi' instanceof String 是 false,因为左边是原始值,不是对象。判断原始类型用 typeof,只有 new String 创建的包装对象才会让 instanceof 返回 true。
试着用自己的话回答

面试官还会怎么问?

给 String.prototype 加方法,所有字符串都能用吗?

能。装箱出来的临时对象原型就是 String.prototype,加上去的方法对所有字符串都生效。但在业务代码里修改内置原型会和第三方库、未来的标准方法冲突,历史上 MooTools 就因为这个导致标准方法被迫改名,所以要写成独立的工具函数。

(1).toString() 为什么要加括号?

1.toString() 会报语法错误,因为词法分析器把 1. 当成了一个带小数点的数字,后面紧跟 toString 就不合法了。加括号、写成 1..toString() 或 1 .toString() 都可以。数字能调方法的原理和字符串一样,是装箱成了 Number 对象。

方法里的 this 是原始值还是包装对象?

取决于是否严格模式。严格模式下,在原始值上调用方法时 this 就是原始值本身;非严格模式下 this 是包装对象。内置方法的行为按规范处理,不受影响,但自己往原型上加的方法里判断 typeof this 时会看到差别。

从一道题,走向一组知识

把知识连起来

值与类型

JS 的 Number 能精确表示的整数范围是多少?超过了会怎样?

(2 ** 53).toString() 这类调用同样靠装箱成 Number 对象,数字的方法都在 Number.prototype 上。

值与类型

为什么 NaN 不等于自身?怎么可靠地判断一个值是不是 NaN?

Number.isNaN 是挂在 Number 构造函数上的静态方法,和包装对象的原型方法是两类东西,可以对照着理解。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • JavaScript data types and data structures

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

本题目录
  1. 先记住这个答案
  2. 装箱的过程和它留下的痕迹
  3. 手动创建包装对象的坑
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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