阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 私有字段提案(#前缀)

私有字段提案(#前缀)

作者:陈川 阅读数:18451人阅读 分类: JavaScript

ECMAScript 6 私有字段提案的背景

ECMAScript 6 引入了类的概念,但类的成员默认是公开的。为了解决封装性问题,TC39 委员会提出了私有字段提案,使用 # 前缀标识私有成员。该提案最终被纳入 ECMAScript 2022 标准,成为 JavaScript 类定义中实现真正私有性的官方方案。

私有字段的基本语法

私有字段通过在字段名前添加 # 前缀来声明,且必须在类顶层显式声明。这种设计确保了私有性在语法层面的强制约束:

class Counter {
  #count = 0;  // 私有字段声明

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

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

const counter = new Counter();
counter.increment();
console.log(counter.value); // 1
console.log(counter.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class

私有字段的特性

  1. 编译时检查:私有字段的访问限制在语法解析阶段就会生效
  2. 硬私有:无法通过任何运行时机制(如 Object.getOwnPropertySymbols)访问
  3. 唯一性:每个类的私有字段都是独立的,子类无法访问父类的私有字段
  4. 必须先声明:所有私有字段必须在类顶层显式声明后才能使用

与 TypeScript private 的区别

TypeScript 的 private 修饰符只是编译时的约束,编译后的代码中这些字段仍然是公开的:

class TSClass {
  private secret = 123;
}

const instance = new TSClass();
console.log(instance['secret']); // 123 (可以绕过类型系统访问)

而 ECMAScript 的私有字段是真正的运行时私有:

class JSClass {
  #secret = 123;
}

const instance = new JSClass();
console.log(instance['#secret']); // undefined

私有字段的继承规则

私有字段遵循严格的封装规则,子类不能访问父类的私有字段:

class Parent {
  #familySecret = 'parent secret';

  reveal() {
    return this.#familySecret;
  }
}

class Child extends Parent {
  #familySecret = 'child secret';  // 这是完全不同的字段

  getParentSecret() {
    // return super.#familySecret; // 语法错误
    return super.reveal();
  }
}

const child = new Child();
console.log(child.getParentSecret()); // "parent secret"
console.log(child.reveal()); // "child secret"

私有字段与方法

私有字段可以与私有方法结合使用,实现完全私有的实现细节:

class Auth {
  #password = 'default';

  #validate(password) {
    return password.length >= 8;
  }

  setPassword(newPassword) {
    if (this.#validate(newPassword)) {
      this.#password = newPassword;
      return true;
    }
    return false;
  }
}

const auth = new Auth();
auth.setPassword('short'); // false
auth.setPassword('longenough'); // true
// auth.#validate('test'); // SyntaxError

私有字段与静态成员

静态私有字段通过在 static 关键字后添加 # 前缀实现:

class Logger {
  static #logLevel = 'INFO';

  static #log(message, level) {
    if (level === this.#logLevel) {
      console.log(message);
    }
  }

  static info(message) {
    this.#log(message, 'INFO');
  }
}

Logger.info('This will be logged');
// Logger.#logLevel = 'DEBUG'; // SyntaxError

私有字段的限制

  1. 必须在类中预先声明

    class Example {
      constructor() {
        this.#value = 42; // SyntaxError: Private field '#value' must be declared in an enclosing class
      }
    }
    
  2. 不能动态创建

    class Dynamic {
      createPrivate() {
        this.#newField = 1; // SyntaxError
      }
    }
    
  3. 不能使用计算属性名

    const fieldName = 'private';
    class Computed {
      #fieldName = 1; // 合法,但字段名就是 "#fieldName"
      #[fieldName] = 2; // SyntaxError
    }
    

私有字段的实际应用场景

  1. 实现内部状态封装

    class Stack {
      #items = [];
      #maxSize = 10;
    
      push(item) {
        if (this.#items.length < this.#maxSize) {
          this.#items.push(item);
        }
      }
    
      pop() {
        return this.#items.pop();
      }
    }
    
  2. 隐藏实现细节

    class Temperature {
      #celsius = 0;
    
      set celsius(value) {
        this.#celsius = value;
      }
    
      get fahrenheit() {
        return this.#celsius * 1.8 + 32;
      }
    }
    
  3. 防止命名冲突

    class SafeMap {
      #data = new Map();
      #size = 0;
    
      set(key, value) {
        this.#data.set(key, value);
        this.#size = this.#data.size;
      }
    }
    

私有字段与 WeakMap 方案的对比

在私有字段出现前,常用 WeakMap 模拟私有性:

const _data = new WeakMap();

class OldSchool {
  constructor() {
    _data.set(this, { count: 0 });
  }

  increment() {
    const data = _data.get(this);
    data.count++;
  }
}

私有字段方案的优势:

  1. 语法更简洁
  2. 性能更好(引擎可以优化存储)
  3. 调试更友好(浏览器开发者工具支持私有字段检查)

私有字段的浏览器兼容性

截至 2023 年,所有现代浏览器都已支持私有字段:

  • Chrome 84+
  • Firefox 90+
  • Safari 14.1+
  • Edge 84+
  • Node.js 12+

对于旧环境,需要通过 Babel 等工具转译。Babel 会将私有字段转换为 WeakMap 形式的实现。

私有字段的设计哲学

TC39 选择 # 前缀而非其他方案(如 private 关键字)的原因:

  1. 显式性:私有成员在语法上明显区别于公共成员
  2. 一致性:与现有语法(如 # 在 CSS 选择器中的使用)保持一定相似性
  3. 可扩展性:为未来可能的装饰器等特性留出设计空间

私有字段与 TypeScript 的配合使用

TypeScript 4.3+ 支持 ECMAScript 私有字段语法,并与类型系统集成:

class TypeSafe {
  #value: number;

  constructor(value: number) {
    this.#value = value;
  }

  square(): number {
    return this.#value ** 2;
  }
}

const instance = new TypeSafe(5);
// instance.#value = "string"; // 编译时和运行时都会报错

私有字段的调试注意事项

现代浏览器开发者工具支持私有字段检查,但有以下特点:

  1. 私有字段显示为 #field 形式
  2. 不能通过控制台直接访问(保持私有性)
  3. 断点调试时可以查看私有字段值
class DebugExample {
  #debugValue = 42;

  method() {
    debugger;
    console.log(this.#debugValue);
  }
}

new DebugExample().method();
// 在调试器中可以查看 #debugValue

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

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

上一篇:类表达式

下一篇:类与原型继承的关系

前端川

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