ES2022引入的私有字段(#)语法为JavaScript类提供了真正的私有属性支持。
私有字段通过#前缀定义,只能在类内部访问,外部无法通过任何方式访问或修改。
相比传统的下划线约定、Symbol或WeakMap方案,私有字段具有更好的封装性、更简洁的语法和更优的性能。
私有字段支持静态字段、方法和getter/setter,但不支持动态添加或子类继承访问。
该特性已被主流浏览器和Node.js支持,TypeScript 3.8+也提供了兼容支持。
私有字段是保护类内部状态的理想选择,使JavaScript的面向对象编程更加完善。
# 是 ES2022 (ES13) 引入的 私有字段(Private Fields) 语法,用于在类中定义真正的私有属性。
1. 基本语法
javascript
class MyClass {
// 私有字段(使用 # 前缀)
#privateField = 0;
// 私有方法
#privateMethod() {
return '私有方法';
}
// 公共方法可以访问私有字段
publicMethod() {
console.log(this.#privateField);
console.log(this.#privateMethod());
}
}
const instance = new MyClass();
instance.publicMethod(); // ✅ 正常访问
console.log(instance.#privateField); // ❌ SyntaxError: 私有字段不能外部访问
2. 为什么需要 # 私有字段?
在 # 出现之前,JavaScript 没有真正的私有属性,通常用以下方式模拟:
javascript
// ❌ 方式1: 下划线约定(只是约定,不是真正的私有)
class User {
constructor(name) {
this._name = name; // 约定为私有,但仍可访问
}
}
const user = new User('Alice');
console.log(user._name); // "Alice" - 仍然可以访问
// ❌ 方式2: 闭包(复杂且性能差)
function createUser(name) {
let _name = name; // 真正的私有
return {
getName() { return _name; },
setName(value) { _name = value; }
};
}
// ✅ 方式3: 私有字段(简洁且真正私有)
class User {
#name;
constructor(name) {
this.#name = name;
}
getName() { return this.#name; }
}
3. 私有字段的特性
3.1 真正的外部不可访问
javascript
class BankAccount {
#balance = 0;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
// ❌ 以下都会报错
console.log(account.#balance); // SyntaxError
console.log(account['#balance']); // undefined(不是真正的属性名)
console.log(Object.keys(account)); // [] - 私有字段不会出现在对象属性中
3.2 硬性私有,无法绕过
javascript
class Secret {
#password = '123456';
getPassword() {
return this.#password;
}
}
const secret = new Secret();
// 所有尝试都无法访问私有字段
console.log(secret.#password); // SyntaxError
console.log(secret['#password']); // undefined
console.log(Reflect.get(secret, '#password')); // undefined
console.log(Object.getOwnPropertyNames(secret)); // []
console.log(JSON.stringify(secret)); // "{}"
3.3 只能在类内部访问
javascript
class Parent {
#private = 'parent private';
parentMethod() {
console.log(this.#private); // ✅ 父类内部可访问
}
}
class Child extends Parent {
childMethod() {
// ❌ 子类不能直接访问父类的私有字段
console.log(this.#private); // SyntaxError
}
}
4. 私有字段 vs 其他方式对比
| 特性 | # 私有字段 |
_ 约定 |
Symbol |
WeakMap |
|---|---|---|---|---|
| 真正私有 | ✅ 完全私有 | ❌ 仅约定 | ⚠️ 可绕过 | ✅ 真正私有 |
| 语法简洁 | ✅ 简洁 | ✅ 简洁 | ⚠️ 稍复杂 | ❌ 复杂 |
| 性能 | ✅ 优秀 | ✅ 优秀 | ✅ 优秀 | ⚠️ 稍差 |
| 调试友好 | ✅ DevTools 支持 | ✅ 可见 | ⚠️ 难调试 | ❌ 难调试 |
| 继承支持 | ⚠️ 子类不能访问 | ✅ 可访问 | ✅ 可访问 | ⚠️ 需手动处理 |
| TypeScript | ✅ 支持(3.8+) | ✅ 支持 | ✅ 支持 | ✅ 支持 |
5. 完整示例对比
javascript
// 方式1: 下划线约定
class UserV1 {
constructor(name, age) {
this._name = name; // 约定私有
this._age = age;
}
getInfo() {
return `${this._name}, ${this._age}`;
}
}
// ❌ 外部仍可访问
const user1 = new UserV1('Alice', 25);
console.log(user1._name); // "Alice" - 没真正隐藏
// 方式2: Symbol
const _name = Symbol('name');
const _age = Symbol('age');
class UserV2 {
constructor(name, age) {
this[_name] = name;
this[_age] = age;
}
getInfo() {
return `${this[_name]}, ${this[_age]}`;
}
}
// ⚠️ 仍可绕过
const user2 = new UserV2('Alice', 25);
const symbols = Object.getOwnPropertySymbols(user2);
console.log(user2[symbols[0]]); // "Alice" - 可以访问
// 方式3: WeakMap(真正的私有)
const privateData = new WeakMap();
class UserV3 {
constructor(name, age) {
privateData.set(this, { name, age });
}
getInfo() {
const data = privateData.get(this);
return `${data.name}, ${data.age}`;
}
}
// ✅ 外部无法访问
const user3 = new UserV3('Alice', 25);
console.log(user3.name); // undefined
console.log(privateData.get(user3)); // 需要 WeakMap 实例,无法访问
// 方式4: 私有字段(最简洁)
class UserV4 {
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
getInfo() {
return `${this.#name}, ${this.#age}`;
}
}
// ✅ 完全私有,语法最简洁
const user4 = new UserV4('Alice', 25);
console.log(user4.#name); // SyntaxError - 无法访问
6. 私有字段的高级用法
6.1 私有方法
javascript
class Calculator {
#result = 0;
// 私有方法
#validateNumber(num) {
if (typeof num !== 'number') {
throw new Error('参数必须是数字');
}
return num;
}
// 私有方法
#updateResult(value) {
this.#result = value;
}
// 公共方法
add(num) {
const validNum = this.#validateNumber(num);
this.#updateResult(this.#result + validNum);
return this;
}
getResult() {
return this.#result;
}
}
const calc = new Calculator();
calc.add(5).add(3);
console.log(calc.getResult()); // 8
// calc.#validateNumber(10); // ❌ 无法调用私有方法
6.2 私有静态字段
javascript
class Counter {
// 私有静态字段
static #count = 0;
constructor() {
Counter.#count++;
}
static getCount() {
return Counter.#count;
}
// 私有静态方法
static #reset() {
Counter.#count = 0;
}
static resetCount() {
Counter.#reset();
}
}
console.log(Counter.getCount()); // 0
new Counter();
new Counter();
console.log(Counter.getCount()); // 2
Counter.resetCount();
console.log(Counter.getCount()); // 0
6.3 私有 getter/setter
javascript
class User {
#firstName;
#lastName;
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
// 私有 getter
get #fullName() {
return `${this.#firstName} ${this.#lastName}`;
}
// 公共方法可以使用私有 getter
getProfile() {
return {
name: this.#fullName,
initials: `${this.#firstName[0]}.${this.#lastName[0]}.`
};
}
}
const user = new User('Alice', 'Lee');
console.log(user.getProfile());
// { name: "Alice Lee", initials: "A.L." }
// user.#fullName ❌ 无法访问
7. 私有字段的注意事项
7.1 不能动态创建
javascript
class MyClass {
#field = 1;
addField() {
// ❌ 不能动态创建私有字段
this.#dynamic = 2; // SyntaxError
}
}
7.2 命名唯一性
javascript
class MyClass {
#value = 1;
method() {
// ✅ 同一个类中可以使用多次
console.log(this.#value);
this.#value = 2;
}
}
// ❌ 不同类之间的同名私有字段不冲突
class OtherClass {
#value = 100; // 这是另一个私有字段
}
7.3 序列化行为
javascript
class User {
#password = 'secret';
name = 'Alice';
toJSON() {
return {
name: this.name,
// 需要手动暴露私有字段
// #password 不会自动序列化
};
}
}
const user = new User();
console.log(JSON.stringify(user)); // {"name":"Alice"}
console.log(Object.keys(user)); // ["name"]
8. 在 Vue 3 中的应用
Composition API 通过模块作用域、闭包或 readonly 实现状态封装
html
<script setup>
import { ref, readonly, computed } from 'vue'
// 1. 模块级私有(最彻底)
const API_KEY = 'secret'
function privateHelper() {}
// 2. 组件内私有(仅当前组件可访问)
const internalState = ref(0)
const internalMethod = () => {}
// 3. 公开状态(使用 readonly 保护)
const publicState = ref('visible')
const protectedState = readonly(publicState)
// 4. 通过 defineExpose 控制暴露
defineExpose({
protectedState,
publicMethod: () => {}
})
</script>
9. 浏览器兼容性
| 环境 | 支持版本 |
|---|---|
| Chrome | 74+ (2019年4月) |
| Firefox | 90+ (2021年7月) |
| Safari | 14.1+ (2021年4月) |
| Edge | 79+ |
| Node.js | 12+ (需要 --harmony 标志) 14.6+ 原生支持 |
| TypeScript | 3.8+ |
10. 总结
| 特性 | 说明 |
|---|---|
| 语法 | #fieldName 定义私有字段 |
| 访问 | 只能在类内部通过 this.#field 访问 |
| 继承 | 子类不能访问父类私有字段 |
| 动态性 | 不能动态添加私有字段 |
| 序列化 | 私有字段不会出现在 Object.keys() 和 JSON.stringify() 中 |
| 适用场景 | 需要真正封装的类属性、内部状态保护 |
一句话总结:
#私有字段是 JavaScript 原生的私有属性语法,提供了真正的封装性,比传统的下划线约定更安全,比 WeakMap 方案更简洁直观。