前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库class 私有字段 # 语法与真正私有
JavaScript对象与原型

class 里的 # 私有字段怎么写?它为什么是真正的私有?

在类体里写 #count = 0 就声明了一个私有字段,只有这个类内部的代码能用 this.#count 读写。它不是挂在对象上的普通属性,类外面连写出 obj.#count 都会报语法错误,所以是真正的私有。

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

私有字段不是属性,是类的内部记录

  1. #count 声明写在类体顶层,可带初始值,每个实例构造时各自生成一份
  2. 词法作用域只有写在这个类体里的代码能访问,子类和外部都不行
  3. #count in obj判断对象有没有这个私有字段,没有也不抛错

私有字段是按类的代码块划分的,同一个类的方法可以读另一个实例的 #count。它私有的是「谁能写这段代码」,不是「哪个对象」。

核心回答

先记住这个答案

在类体顶层写 #count = 0 就声明了私有字段,类里的方法用 this.#count 读写。它私有得很彻底:类外面写 obj.#count 在解析阶段就是 SyntaxError,代码根本跑不起来;Object.keys、for...in、JSON.stringify、Reflect.ownKeys 都拿不到它,因为它根本不是对象上的属性。以前用下划线 _count 或 TypeScript 的 private,都只是约定或编译期检查,运行时照样能读到。

  • 私有字段必须先在类体里声明,名字带 #
  • 类外访问 #x 会报 SyntaxError
  • 反射和 JSON 都看不到 # 私有字段
  • 用 #x in obj 判断对象是不是本类实例

最小例子:私有字段读写和品牌检查

下面这个计数器把 #count 藏起来,外部只能通过 increment() 改它。

私有字段在外部完全不可见JavaScript
class Counter {
  #count = 0 // 私有字段,必须先在类体里声明

  increment() {
    this.#count++
    return this.#count
  }

  static isCounter(obj) {
    return #count in obj // 有这个私有字段才返回 true,不会抛错
  }

  static peek(obj) {
    return obj.#count // obj 身上没有这个字段就抛 TypeError
  }
}

const c = new Counter()
console.log(c.increment())
console.log(Object.keys(c).length, JSON.stringify(c))
console.log(Counter.isCounter(c), Counter.isCounter({}))
try {
  Counter.peek({ '#count': 99 }) // 字符串键 '#count' 和私有字段毫无关系
} catch (e) {
  console.log(e.name)
}
查看输出与解释
1
0 {}
true false
TypeError

第二行说明私有字段不在属性列表里,序列化也带不出去。最后一段传进去的对象虽然有个叫 '#count' 的字符串键,但那只是普通属性,和私有字段不是一回事,读取时引擎发现这个对象没有 Counter 的私有字段,直接抛 TypeError。

它凭什么是真正的私有

私有字段靠的是词法作用域,也就是「代码写在哪里」。#count 这个名字只在 Counter 的类体里有效,出了这对花括号就不存在,所以类外面写 obj.#count 属于引用了一个没声明的名字,解析器直接报错。这和变量作用域很像:函数里的局部变量,外面怎么都拿不到。

它也不是属性。每个实例在构造时,引擎会在对象内部单独记一份私有数据,和普通属性表分开存放。所以所有基于属性的反射手段,比如 Object.getOwnPropertyNames、Object.getOwnPropertyDescriptors,都看不到它,delete 也删不掉它。

私有方法和静态私有成员也是同一套规则:#format() {} 是私有方法,static #instances = 0 是静态私有字段,一样只能在类体里用。

和 TypeScript private、闭包比有什么不同

TypeScript 的 private 只在编译时检查,编译成 JavaScript 后就是普通属性,obj['count'] 照样能读到,console.log 也能打印出来。# 是运行时的保证,编译前后都拿不到。

闭包方案是在构造函数里定义局部变量,再把方法挂到实例上。它也能做到真正私有,但每个实例都要各自创建一份方法,没法放到原型上共享。私有字段的方法依然在原型上,内存更省,写法也更像普通类。

回答前,多想一步

容易答错的地方

以为子类能访问父类的 # 字段
不能。子类的类体是另一个作用域,写 this.#count 会报语法错误。父类想让子类用,只能提供普通方法或 getter。
以为 #count 和 this['#count'] 是同一个东西
this['#count'] 是一个名字叫 '#count' 的普通字符串属性,任何人都能读写。私有字段不能用方括号动态访问,只能写 this.#count。
以为 Proxy 包一层不影响私有字段
Proxy 包住实例后,方法里的 this 指向代理对象,而代理对象身上没有私有字段,this.#count 会抛 TypeError。Vue 3 的响应式对象包住带私有字段的类实例时就会踩到这个坑。
试着用自己的话回答

面试官还会怎么问?

怎么判断一个对象是不是某个类创建的?

instanceof 可以被 Symbol.hasInstance 或改原型骗过去,更可靠的是在类里写一个静态方法,用 #field in obj 判断。只有真正经过这个类构造的对象才会有这个私有字段。

同一个类的两个实例能互相读私有字段吗?

能。比如 equals(other) { return this.#count === other.#count } 完全合法。访问权限按类的代码划分,只要代码写在类体里,就能读任何本类实例的私有字段。

私有字段没声明直接在构造函数里 this.#x = 1 可以吗?

不行,会报语法错误。私有字段必须先在类体顶层声明,这一点和普通字段不同,普通属性可以随时 this.x = 1 动态添加。

从一道题,走向一组知识

把知识连起来

对象与原型

ES5 继承为什么要写 Child.prototype = new Parent()?Object.create 改进了什么?

ES5 时代没有私有字段,只能靠闭包或下划线约定,对比着看能明白 # 解决了什么。

对象与原型

Object.preventExtensions() 之后还能给对象加属性吗?怎么判断?

Object.preventExtensions 管的是普通属性,私有字段不在属性表里,两者可以对照理解。

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 最小例子:私有字段读写和品牌检查
  3. 它凭什么是真正的私有
  4. 和 TypeScript private、闭包比有什么不同
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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