阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 私有字段与命名规则

私有字段与命名规则

作者:陈川 阅读数:30415人阅读 分类: TypeScript

私有字段的概念与作用

TypeScript中的私有字段是类中只能在类内部访问的成员。它们的主要作用是封装内部实现细节,防止外部代码直接修改或依赖这些细节。私有字段通过private修饰符声明:

class Person {
  private _age: number;
  
  constructor(age: number) {
    this._age = age;
  }
  
  getAge(): number {
    return this._age;
  }
}

const person = new Person(30);
console.log(person._age); // 错误:属性"_age"为私有属性,只能在类"Person"中访问

ECMAScript规范也引入了原生私有字段语法,使用#前缀:

class Person {
  #age: number;
  
  constructor(age: number) {
    this.#age = age;
  }
}

命名规则与约定

TypeScript社区对于私有字段的命名有一些常见约定:

  1. 下划线前缀:最传统的命名方式
private _internalValue: string;
  1. 无前缀的private修饰符:TypeScript特有的方式
private internalValue: string;
  1. 哈希前缀:ES标准私有字段
#internalValue: string;

对于存取器方法,常见的命名模式是:

class Temperature {
  private _celsius: number = 0;
  
  get celsius(): number {
    return this._celsius;
  }
  
  set celsius(value: number) {
    this._celsius = value;
  }
}

不同私有字段实现的比较

TypeScript提供了多种实现私有字段的方式,各有优缺点:

  1. 编译时私有(private修饰符)
class Example {
  private internal: string;
}
  • 优点:语法简洁
  • 缺点:只在编译时检查,运行时仍可访问
  1. WeakMap私有模式
const _internal = new WeakMap();

class Example {
  constructor() {
    _internal.set(this, 'secret');
  }
}
  • 优点:真正的运行时私有
  • 缺点:语法复杂,性能开销
  1. ES私有字段(#前缀)
class Example {
  #internal: string;
}
  • 优点:真正的运行时私有,语法相对简洁
  • 缺点:需要较新的JavaScript引擎支持

实际应用场景示例

考虑一个银行账户类的实现:

class BankAccount {
  private _balance: number = 0;
  #accountNumber: string;
  
  constructor(initialBalance: number, accountNumber: string) {
    this._balance = initialBalance;
    this.#accountNumber = accountNumber;
  }
  
  deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error('存款金额必须大于零');
    }
    this._balance += amount;
  }
  
  get balance(): number {
    return this._balance;
  }
  
  get maskedAccountNumber(): string {
    return this.#accountNumber.slice(-4).padStart(this.#accountNumber.length, '*');
  }
}

类型系统与私有字段

TypeScript的类型检查对私有字段有特殊处理:

class Base {
  private x = 1;
}

class Derived extends Base {
  private x = 2; // 错误:属性'x'在类型'Derived'中为私有属性
}

class Different {
  private x = 3;
}

let base: Base = new Different(); // 错误:类型不兼容

设计模式中的私有字段应用

观察者模式中使用私有字段保护观察者列表:

class Subject {
  private observers: Observer[] = [];
  #state: number;
  
  attach(observer: Observer): void {
    this.observers.push(observer);
  }
  
  setState(state: number): void {
    this.#state = state;
    this.notify();
  }
  
  private notify(): void {
    for (const observer of this.observers) {
      observer.update(this.#state);
    }
  }
}

测试中的私有字段处理

测试私有字段时需要特殊处理:

class MyClass {
  private _value = 42;
  
  getValue(): number {
    return this._value;
  }
}

// 测试代码
describe('MyClass', () => {
  it('should access private field', () => {
    const instance = new MyClass();
    expect((instance as any)._value).toBe(42);
  });
});

更好的做法是使用类型断言:

interface MyClassWithPrivate {
  _value: number;
}

const instance = new MyClass() as unknown as MyClassWithPrivate;
expect(instance._value).toBe(42);

性能考量

不同私有字段实现方式有性能差异:

  1. 传统下划线前缀字段访问最快
  2. ES私有字段(#)在现代引擎中优化良好
  3. WeakMap实现方式性能最差
// 性能测试示例
class PerformanceTest {
  public publicField = 0;
  private _privateField = 0;
  #hashPrivateField = 0;
  
  testPublic() {
    for (let i = 0; i < 1e6; i++) {
      this.publicField += i;
    }
  }
  
  testPrivate() {
    for (let i = 0; i < 1e6; i++) {
      this._privateField += i;
    }
  }
  
  testHashPrivate() {
    for (let i = 0; i < 1e6; i++) {
      this.#hashPrivateField += i;
    }
  }
}

与JavaScript的互操作性

TypeScript私有字段与JavaScript交互时需要注意:

// TypeScript类
class TSClass {
  private secret = 'typescript';
}

// JavaScript代码
const instance = new TSClass();
console.log(instance.secret); // 运行时可以访问,但TypeScript会报错

而ES私有字段在JavaScript中真正不可访问:

class ESClass {
  #secret = 'ecmascript';
}

const esInstance = new ESClass();
console.log(esInstance.#secret); // 语法错误

装饰器与私有字段

装饰器可以用于增强私有字段的功能:

function logAccess(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalGet = descriptor.get;
  descriptor.get = function() {
    console.log(`Accessing ${propertyKey}`);
    return originalGet?.call(this);
  };
}

class User {
  private _name: string;
  
  constructor(name: string) {
    this._name = name;
  }
  
  @logAccess
  get name(): string {
    return this._name;
  }
}

继承与私有字段

私有字段在继承中的行为:

class Parent {
  private parentSecret = 'parent';
  #realSecret = 'really private';
  
  revealSecret(): string {
    return this.parentSecret;
  }
}

class Child extends Parent {
  private parentSecret = 'child'; // 允许,因为父类的parentSecret对这个类不可见
  
  tryReveal(): string {
    return this.#realSecret; // 错误:属性"#realSecret"在类"Parent"中为私有属性
  }
}

接口与私有字段

接口不能定义私有字段,但可以要求实现类具有特定私有字段:

interface IHasSecret {
  getSecret(): string;
}

class SecretKeeper implements IHasSecret {
  private _secret = 'confidential';
  
  getSecret(): string {
    return this._secret;
  }
}

模块系统与私有字段

在不同模块中,TypeScript的private修饰符仍然有效:

// moduleA.ts
export class A {
  private internal = 123;
}

// moduleB.ts
import { A } from './moduleA';

const a = new A();
console.log(a.internal); // 错误:属性"internal"为私有属性

反射与私有字段

通过反射API可以绕过TypeScript的private限制:

class PrivateHolder {
  private secret = 'hidden';
}

const instance = new PrivateHolder();
console.log(Reflect.get(instance, 'secret')); // 输出: 'hidden'

常见错误与陷阱

  1. 意外暴露私有字段
class Oops {
  private _value = 42;
  
  getValue() {
    return this; // 意外返回了整个实例
  }
}

const oops = new Oops();
console.log(oops.getValue()._value); // 可以访问私有字段
  1. JSON序列化问题
class User {
  private password = 'secret';
  name = 'Alice';
}

const user = new User();
console.log(JSON.stringify(user)); // 输出: {"name":"Alice"}

工具链支持

  1. TypeScript编译器选项
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictPropertyInitialization": true
  }
}
  1. ESLint规则
{
  "rules": {
    "@typescript-eslint/explicit-member-accessibility": ["error"],
    "@typescript-eslint/naming-convention": [
      "error",
      {
        "selector": "property",
        "modifiers": ["private"],
        "format": ["camelCase"],
        "leadingUnderscore": "allow"
      }
    ]
  }
}

历史演变与未来方向

TypeScript私有字段的演变过程:

  1. 早期版本:只有private修饰符
  2. TypeScript 3.8:支持ECMAScript私有字段语法
  3. 未来可能:更严格的运行时私有检查
// TypeScript私有字段
class OldWay {
  private legacy = 'old';
}

// ES私有字段
class NewWay {
  #modern = 'new';
}

与其他语言的比较

  1. Java
class JavaExample {
  private int value;
}
  1. C#
class CSharpExample {
  private string _value;
}
  1. Python(约定而非强制):
class PythonExample:
    def __init__(self):
        self._protected = 'convention'
        self.__private = 'name mangled'

大型项目中的最佳实践

在大型TypeScript项目中推荐:

  1. 统一使用一种私有字段风格(推荐ES私有字段)
  2. 对真正的内部状态使用私有字段
  3. 对需要子类访问的成员使用protected
  4. 建立团队命名约定文档
// 推荐的项目风格
class ProjectStandard {
  // 公共API
  public apiMethod() {}
  
  // 子类可访问
  protected internalMethod() {}
  
  // 真正私有的实现细节
  #coreLogic() {}
  
  // TypeScript私有,团队约定使用
  private _legacyReason = 'migration';
}

编译输出差异

不同私有字段语法编译后的JavaScript差异:

  1. TypeScript private
class A {
  private x = 1;
}

编译为:

class A {
  constructor() {
    this.x = 1;
  }
}
  1. ES private
class B {
  #x = 1;
}

编译为:

class B {
  constructor() {
    _x.set(this, 1);
  }
}
const _x = new WeakMap();

调试与私有字段

Chrome DevTools对私有字段的支持:

  1. ES私有字段显示为#fieldName
  2. TypeScript私有字段显示为普通属性
  3. 断点调试时可以查看私有字段值
class DebugExample {
  private tsPrivate = 'typescript';
  #esPrivate = 'ecmascript';
  
  debug() {
    debugger; // 在这里检查变量
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌