JavaScript 私有字段
简化版
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 #field | TS 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,就能把核心差异讲清楚。