私有字段
概述
私有字段(Private Fields)是 ES2022(ES2022)引入的 JavaScript 特性,使用 # 前缀语法来定义真正私有的类成员。与 TypeScript 的 private 修饰符不同,私有字段在运行时也是真正私有的,编译后的 JavaScript 代码中无法访问。私有字段提供了更强的封装性,确保类的内部实现细节不会被外部代码意外访问或修改。TypeScript 完全支持私有字段语法,并提供了完整的类型检查支持。
基本语法
定义私有字段
使用 # 前缀定义私有字段,私有字段必须在类中声明,不能通过 this 动态添加:
typescript
class BankAccount {
// 私有字段:使用 # 前缀
#balance: number;
#accountNumber: string;
// 公共属性
public owner: string;
constructor(owner: string, accountNumber: string, initialBalance: number = 0) {
this.owner = owner;
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// 公共方法访问私有字段
public deposit(amount: number): void {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited ${amount}. New balance: ${this.#balance}`);
}
}
public withdraw(amount: number): boolean {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew ${amount}. Remaining balance: ${this.#balance}`);
return true;
}
return false;
}
public getBalance(): number {
return this.#balance; // 类内部可以访问私有字段
}
public getAccountNumber(): string {
return this.#maskAccountNumber(); // 通过方法访问,不直接暴露
}
// 私有方法(使用 # 前缀)
#maskAccountNumber(): string {
return `****${this.#accountNumber.slice(-4)}`;
}
}
const account = new BankAccount("Alice", "1234567890", 1000);
account.deposit(500); // ✅ 可以调用公共方法
console.log(account.getBalance()); // ✅ 1500
console.log(account.getAccountNumber()); // ✅ "****7890"
// ❌ 错误:不能访问私有字段
// console.log(account.#balance); // Error: Property '#balance' is not accessible outside class 'BankAccount'
// console.log(account.#accountNumber); // Error: Property '#accountNumber' is not accessible outside class 'BankAccount'
// account.#maskAccountNumber(); // Error: Property '#maskAccountNumber' is not accessible outside class 'BankAccount'私有字段的声明要求
私有字段必须在类体中声明,不能在构造函数或其他方法中动态添加:
typescript
class Example {
// ✅ 正确:在类体中声明私有字段
#value: string;
constructor(value: string) {
this.#value = value; // ✅ 可以赋值
}
// ❌ 错误:不能在方法中动态添加私有字段
// addPrivateField() {
// this.#newField = "value"; // Error: Property '#newField' does not exist
// }
}
// ❌ 错误:不能在类外部添加私有字段
// const example = new Example("test");
// example.#newField = "value"; // Error: Property '#newField' does not exist私有字段 vs private 修饰符
TypeScript 提供了两种私有化方式:private 修饰符和私有字段(#)。它们有重要的区别:
编译时私有 vs 运行时私有
private 修饰符:只在编译时检查,编译后的 JavaScript 中仍然可以访问:
typescript
class ExampleWithPrivate {
private value: string = "secret";
public getValue(): string {
return this.value;
}
}
const example = new ExampleWithPrivate();
// TypeScript 编译错误,但编译后的 JavaScript 可以访问
// console.log(example.value); // TypeScript Error: Property 'value' is private
// 编译后的 JavaScript 代码类似:
// class ExampleWithPrivate {
// constructor() {
// this.value = "secret"; // 仍然是公共属性
// }
// }
// 运行时可以访问:example.value私有字段(#):在编译时和运行时都是私有的:
typescript
class ExampleWithHash {
#value: string = "secret";
public getValue(): string {
return this.#value;
}
}
const example = new ExampleWithHash();
// ❌ 编译时错误
// console.log(example.#value); // Error: Property '#value' is not accessible
// ❌ 运行时也无法访问(真正的私有)
// 编译后的 JavaScript 使用 WeakMap 实现,外部无法访问对比示例
typescript
// 使用 private 修饰符
class UserWithPrivate {
private password: string;
public username: string;
constructor(username: string, password: string) {
this.username = username;
this.password = password;
}
public verifyPassword(input: string): boolean {
return this.password === input;
}
}
// 使用私有字段
class UserWithHash {
#password: string;
public username: string;
constructor(username: string, password: string) {
this.username = username;
this.#password = password;
}
public verifyPassword(input: string): boolean {
return this.#password === input;
}
}
const user1 = new UserWithPrivate("alice", "secret123");
const user2 = new UserWithHash("bob", "secret456");
// 编译后的 JavaScript 中:
// user1.password 可能仍然可以访问(取决于编译目标)
// user2.#password 完全无法访问(真正的私有)
// TypeScript 中两者都不能直接访问
// console.log(user1.password); // TypeScript Error
// console.log(user2.#password); // TypeScript Error选择建议
| 特性 | private 修饰符 | 私有字段(#) |
|---|---|---|
| 编译时检查 | ✅ | ✅ |
| 运行时私有 | ❌ | ✅ |
| 性能 | 更好(直接属性访问) | 稍差(WeakMap 实现) |
| 兼容性 | 所有 TypeScript 版本 | 需要 ES2022+ |
| 子类访问 | ❌ | ❌ |
| 推荐场景 | 一般封装需求 | 需要真正运行时私有 |
提示
- 如果只需要编译时的类型安全,使用
private修饰符即可 - 如果需要真正的运行时私有性(如库开发、安全敏感场景),使用私有字段(
#) - 私有字段在编译后的代码中无法通过任何方式访问,提供了最强的封装性
使用示例
示例 1:用户认证系统
typescript
class User {
#password: string; // 私有字段:密码
#salt: string; // 私有字段:盐值
public username: string;
public email: string;
constructor(username: string, email: string, password: string) {
this.username = username;
this.email = email;
this.#salt = this.#generateSalt();
this.#password = this.#hashPassword(password);
}
// 公共方法:验证密码
public verifyPassword(inputPassword: string): boolean {
const hashedInput = this.#hashPassword(inputPassword);
return this.#password === hashedInput;
}
// 公共方法:更改密码
public changePassword(oldPassword: string, newPassword: string): boolean {
if (this.verifyPassword(oldPassword)) {
this.#password = this.#hashPassword(newPassword);
return true;
}
return false;
}
// 私有方法:生成盐值
#generateSalt(): string {
return Math.random().toString(36).substring(2, 15);
}
// 私有方法:哈希密码(简化示例)
#hashPassword(password: string): string {
// 实际应用中应使用专业的加密库
return `${password}_${this.#salt}_hashed`;
}
// 公共方法:获取用户信息(不暴露敏感数据)
public getPublicInfo(): { username: string; email: string } {
return {
username: this.username,
email: this.email
};
}
}
const user = new User("alice", "alice@example.com", "myPassword123");
console.log(user.username); // ✅ "alice"
console.log(user.getPublicInfo()); // ✅ { username: "alice", email: "alice@example.com" }
console.log(user.verifyPassword("myPassword123")); // ✅ true
console.log(user.verifyPassword("wrong")); // ✅ false
// ❌ 错误:无法访问私有字段
// console.log(user.#password); // Error
// console.log(user.#salt); // Error
// user.#hashPassword("test"); // Error示例 2:缓存系统
typescript
class Cache {
#cache: Map<string, { value: any; timestamp: number }>;
#maxAge: number; // 最大缓存时间(毫秒)
constructor(maxAge: number = 60000) { // 默认 1 分钟
this.#cache = new Map();
this.#maxAge = maxAge;
}
// 公共方法:设置缓存
public set(key: string, value: any): void {
this.#cache.set(key, {
value,
timestamp: Date.now()
});
this.#cleanExpired(); // 清理过期缓存
}
// 公共方法:获取缓存
public get(key: string): any | null {
const item = this.#cache.get(key);
if (!item) {
return null;
}
// 检查是否过期
if (this.#isExpired(item.timestamp)) {
this.#cache.delete(key);
return null;
}
return item.value;
}
// 公共方法:清除所有缓存
public clear(): void {
this.#cache.clear();
}
// 公共方法:获取缓存统计
public getStats(): { size: number; maxAge: number } {
return {
size: this.#cache.size,
maxAge: this.#maxAge
};
}
// 私有方法:检查是否过期
#isExpired(timestamp: number): boolean {
return Date.now() - timestamp > this.#maxAge;
}
// 私有方法:清理过期缓存
#cleanExpired(): void {
const now = Date.now();
for (const [key, item] of this.#cache.entries()) {
if (this.#isExpired(item.timestamp)) {
this.#cache.delete(key);
}
}
}
}
const cache = new Cache(5000); // 5 秒过期
cache.set("user:1", { name: "Alice", age: 30 });
cache.set("user:2", { name: "Bob", age: 25 });
console.log(cache.get("user:1")); // ✅ { name: "Alice", age: 30 }
console.log(cache.getStats()); // ✅ { size: 2, maxAge: 5000 }
// 等待 6 秒后
setTimeout(() => {
console.log(cache.get("user:1")); // ✅ null(已过期)
console.log(cache.getStats()); // ✅ { size: 0, maxAge: 5000 }
}, 6000);
// ❌ 错误:无法直接访问内部缓存
// console.log(cache.#cache); // Error
// cache.#cleanExpired(); // Error示例 3:计数器类(防止外部修改)
typescript
class Counter {
#count: number = 0; // 私有字段:防止外部直接修改
#step: number; // 私有字段:步长
constructor(initialValue: number = 0, step: number = 1) {
this.#count = initialValue;
this.#step = step;
}
// 公共方法:增加
public increment(): number {
this.#count += this.#step;
return this.#count;
}
// 公共方法:减少
public decrement(): number {
this.#count -= this.#step;
return this.#count;
}
// 公共方法:重置
public reset(): void {
this.#count = 0;
}
// 公共方法:获取当前值
public getValue(): number {
return this.#count;
}
// 公共方法:设置步长
public setStep(step: number): void {
if (step > 0) {
this.#step = step;
}
}
// 公共方法:获取步长
public getStep(): number {
return this.#step;
}
}
const counter = new Counter(10, 2);
console.log(counter.getValue()); // ✅ 10
counter.increment(); // ✅ 12
counter.increment(); // ✅ 14
counter.decrement(); // ✅ 12
console.log(counter.getStep()); // ✅ 2
// ❌ 错误:无法直接修改私有字段
// counter.#count = 100; // Error: Property '#count' is not accessible
// counter.#step = 5; // Error: Property '#step' is not accessible
// ✅ 只能通过公共方法修改
counter.setStep(3);
counter.increment(); // ✅ 15(12 + 3)私有字段与继承
私有字段在子类中也是不可访问的,这与 private 修饰符的行为一致:
typescript
class Base {
#privateField: string = "base private";
private privateModifier: string = "base private modifier";
protected protectedField: string = "base protected";
public getPrivateField(): string {
return this.#privateField; // ✅ 类内部可以访问
}
public getPrivateModifier(): string {
return this.privateModifier; // ✅ 类内部可以访问
}
}
class Derived extends Base {
#derivedPrivate: string = "derived private";
public testAccess(): void {
// ✅ 可以访问 protected
console.log(this.protectedField);
// ❌ 错误:不能访问父类的私有字段
// console.log(this.#privateField); // Error: Property '#privateField' is not accessible
// ❌ 错误:不能访问父类的 private 修饰符成员
// console.log(this.privateModifier); // Error: Property 'privateModifier' is private
// ✅ 可以通过公共方法访问
console.log(this.getPrivateField()); // ✅ "base private"
console.log(this.getPrivateModifier()); // ✅ "base private modifier"
// ✅ 可以访问自己的私有字段
console.log(this.#derivedPrivate); // ✅ "derived private"
}
}
const derived = new Derived();
derived.testAccess();
// ❌ 错误:外部无法访问任何私有成员
// console.log(derived.#privateField); // Error
// console.log(derived.#derivedPrivate); // Error
// console.log(derived.privateModifier); // Error私有字段与静态成员
私有字段也可以是静态的,使用 static # 语法:
typescript
class Logger {
static #logs: string[] = []; // 静态私有字段
static #maxLogs: number = 100;
// 静态公共方法
static log(message: string): void {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}`;
Logger.#logs.push(logEntry);
// 限制日志数量
if (Logger.#logs.length > Logger.#maxLogs) {
Logger.#logs.shift(); // 移除最旧的日志
}
console.log(logEntry);
}
// 静态公共方法:获取日志
static getLogs(): string[] {
return [...Logger.#logs]; // 返回副本,防止外部修改
}
// 静态公共方法:清除日志
static clearLogs(): void {
Logger.#logs = [];
}
// 静态公共方法:设置最大日志数
static setMaxLogs(max: number): void {
if (max > 0) {
Logger.#maxLogs = max;
}
}
// 静态私有方法
static #formatLog(message: string): string {
return `[${new Date().toISOString()}] ${message}`;
}
}
Logger.log("Application started");
Logger.log("User logged in");
Logger.log("Data saved");
console.log(Logger.getLogs()); // ✅ 获取所有日志
Logger.clearLogs(); // ✅ 清除日志
// ❌ 错误:无法访问静态私有字段
// console.log(Logger.#logs); // Error
// Logger.#formatLog("test"); // Error类型检查示例
常见错误
typescript
// ❌ 错误:外部访问私有字段
class Example {
#value: string = "secret";
}
const example = new Example();
console.log(example.#value);
// Error: Property '#value' is not accessible outside class 'Example'
// ❌ 错误:未声明的私有字段
class Example2 {
#value: string = "value";
setValue() {
this.#newValue = "new"; // Error: Property '#newValue' does not exist
}
}
// ❌ 错误:子类访问父类私有字段
class Parent {
#parentPrivate: string = "parent";
}
class Child extends Parent {
getParentPrivate(): string {
return this.#parentPrivate; // Error: Property '#parentPrivate' is not accessible
}
}
// ❌ 错误:在类外部声明私有字段
// const obj = {};
// obj.#field = "value"; // Error: Private fields can only be declared in a class正确写法
typescript
// ✅ 正确:通过公共方法访问私有字段
class Example {
#value: string = "secret";
public getValue(): string {
return this.#value;
}
public setValue(value: string): void {
this.#value = value;
}
}
const example = new Example();
console.log(example.getValue()); // ✅ "secret"
example.setValue("new secret");
console.log(example.getValue()); // ✅ "new secret"
// ✅ 正确:在类中正确声明和使用私有字段
class Counter {
#count: number = 0;
increment(): void {
this.#count++; // ✅ 类内部可以访问
}
getCount(): number {
return this.#count; // ✅ 类内部可以访问
}
}
// ✅ 正确:通过公共方法让子类间接访问
class Base {
#value: string = "base";
protected getValue(): string {
return this.#value; // ✅ 通过受保护方法暴露
}
}
class Derived extends Base {
public showValue(): string {
return this.getValue(); // ✅ 通过继承的方法访问
}
}注意事项
提示
- 私有字段使用
#前缀,必须在类体中声明 - 私有字段在运行时也是真正私有的,提供了最强的封装性
- 私有字段可以是实例字段或静态字段
- 私有字段也支持方法(私有方法)
- 私有字段在编译后的代码中无法通过任何方式访问
注意
- 私有字段需要 ES2022+ 支持,确保编译目标正确配置
- 私有字段在子类中不可访问,如果需要子类访问,使用
protected修饰符 - 私有字段不能通过
this动态添加,必须在类体中声明 - 私有字段的性能略低于普通属性(使用 WeakMap 实现)
- 私有字段与
private修饰符可以同时使用,但通常选择一种即可
重要
- 运行时私有性:私有字段是唯一提供真正运行时私有的方式
- 安全性:对于安全敏感的代码(如密码、密钥),优先使用私有字段
- 库开发:开发供他人使用的库时,私有字段可以防止外部代码访问内部实现
- 性能考虑:如果性能敏感且不需要运行时私有,可以使用
private修饰符 - 兼容性:确保目标环境支持 ES2022+,或使用适当的转译工具
信息
何时使用私有字段(#):
需要运行时私有性
- 开发库或框架
- 安全敏感的应用
- 需要防止外部代码访问内部实现
真正的封装需求
- 不希望任何外部代码(包括运行时)访问
- 需要最强的封装保证
何时使用 private 修饰符:
只需要编译时类型安全
- 一般应用开发
- 性能敏感的场景
- 不需要运行时私有性
兼容性要求
- 需要支持较旧的 JavaScript 版本
- 不需要 ES2022+ 特性
最佳实践:
- 对于新项目,如果目标环境支持,优先考虑私有字段
- 对于安全敏感的数据,必须使用私有字段
- 对于一般封装需求,
private修饰符通常足够
相关链接
- 访问修饰符 - 了解
private、protected、public修饰符 - 类基础 - 学习类的基础概念
- 类继承 - 了解类的继承机制
- 静态成员 - 学习静态成员的使用
- TypeScript 官方文档 - 私有字段