← 返回题目列表

JavaScript 私有字段

高频 中等 第 13 / 28 题 更新于 2026/07/28
ES2022Class私有字段

简化版

JavaScript 的 #field 是运行时真正的私有字段,只能在类内部访问;TypeScript 的 private 主要是编译期类型检查,编译后通常仍可能通过属性名访问。

详细版

ES 私有字段写法:

class Counter {
  #count = 0;

  inc() {
    this.#count++;
  }
}

它的特点:

  • 字段名前带 #,不是普通字符串属性。
  • 类外访问 obj.#count 会语法报错。
  • 私有字段有品牌检查,不属于原型链普通属性查找。
  • 和 TypeScript private 不同,# 是 JavaScript 运行时语义。

面试中要把“语法私有”和“类型系统私有”区分开。

完整版教学

一、为什么需要真正的私有字段

传统 JavaScript 没有真正的类私有字段。 开发者常用 _name 表示约定私有,或用闭包、WeakMap 隐藏数据。 这些方案要么只是约定,要么写法复杂。

class User {
  constructor(name) {
    this._name = name;
  }
}

const u = new User('Alice');
console.log(u._name); // 仍然可以访问

_name 只是团队约定。 如果 10 个模块都能直接改它,类就无法维护自己的不变量。 #name 的出现,是为了把私有性变成语言级规则。

二、#field 是运行时私有,不是普通属性名

私有字段必须在类体中声明。 类外部不能读取,也不能通过字符串属性名访问。 它不是对象上的普通 key。

class Counter {
  #count = 0;
  getCount() {
    return this.#count;
  }
}

const c = new Counter();
console.log(c.getCount()); // 0
console.log(c['#count']);  // undefined

如果写 c.#count,代码在语法阶段就会报错。 这比运行时返回 undefined 更强,因为错误更早暴露。

三、私有字段有品牌检查

私有字段访问会检查对象是否由声明该私有字段的类创建。 这常被称为品牌检查。 即使另一个对象形状相似,也不能伪装成拥有该私有字段。

class Box {
  #value = 1;
  read(other) {
    return other.#value;
  }
}

const box = new Box();
box.read(box); // 1
box.read({});  // TypeError

流程可以理解为:

访问 other.#value
  -> 检查 other 是否有 Box 的私有品牌
  -> 有:读取
  -> 无:抛错

这也是它区别于普通属性查找的重要点。

四、它和 TypeScript private 的差异

TypeScript 的 private 主要服务于类型检查。 如果编译目标和配置没有使用 ECMAScript 私有字段,运行时对象上仍可能存在普通属性。 而 #field 是 JavaScript 引擎层面的私有语义。

维度JS #fieldTS private
生效阶段运行时语义编译期检查为主
类外访问语法或运行时报错TS 报错,JS 可能可访问
属性形态私有槽位常见输出为普通属性
是否需要 TS不需要需要 TypeScript

如果项目使用 TypeScript,也可以写 #field。 这时私有性来自 JavaScript 语言本身,而不是仅靠类型系统约束。

五、私有方法和私有静态成员

私有能力不只用于字段。 类里还可以定义私有方法和私有静态字段。 它们适合封装只服务于类内部的实现细节。

class TokenStore {
  static #prefix = 'token:';
  #cache = new Map();

  #key(id) {
    return `${TokenStore.#prefix}${id}`;
  }

  set(id, value) {
    this.#cache.set(this.#key(id), value);
  }
}

这个例子里有 1 个私有静态字段、1 个私有实例字段、1 个私有方法。 外部只能通过公开方法操作,不需要知道内部 key 的拼接规则。

六、工程中什么时候该用它

私有字段适合保护类的不变量。 例如计数器不能被外部改成负数,缓存结构不能被外部替换,状态机内部状态只能通过合法迁移改变。 如果只是为了少暴露一点属性,过度使用私有字段也会让测试和调试变麻烦。

class Counter {
  #count = 0;

  inc() {
    this.#count += 1;
  }

  value() {
    return this.#count;
  }
}

记忆钩子:private 像门口贴纸,# 像语言给类装上的锁;是否使用取决于你要不要运行时强约束。

在前端组件代码里,很多状态已由框架管理,未必都要封进 class 私有字段。 在 SDK、工具库、领域模型里,它的价值更明显。

七、常见误区与追问

  • 误区:#count 等价于对象上的 '#count' 属性。 私有字段不是普通字符串 key,obj['#count'] 访问不到它。
  • 误区:TypeScript private 一定能阻止运行时访问。 它主要是类型检查,运行时是否能访问取决于编译输出和写法。
  • 误区:私有字段可以在构造函数里随便首次创建。 私有字段需要在类体中声明,访问未声明私有名会语法报错。
  • 追问:私有字段是否会出现在 Object.keys 中? 不会,它不是普通可枚举属性。
  • 追问:为什么 other.#x 可能报 TypeError? 因为访问私有字段时会做品牌检查,普通对象没有对应私有槽位。
  • 追问:私有字段适合所有类属性吗? 不适合;它更适合保护不变量和内部结构,过度使用会增加调试与测试成本。

八、加强记忆

区分两类私有性:TS private 偏编译期约束,JS #field 是运行时语义。答题时先说语法和访问限制,再说品牌检查,最后用表格对比 TypeScript private,就能把核心差异讲清楚。