Skip to content

私有属性和方法

class 中的私有成员用于把某些状态和实现细节限制在类内部访问。它解决的是“这个字段只允许类自己使用,不希望外部直接碰”的问题。

私有字段的写法

现代 JavaScript 使用 # 前缀声明私有字段:

js
class Counter {
  #count = 0

  increase() {
    this.#count++
  }

  getCount() {
    return this.#count
  }
}

这里的 #count 只能在 Counter 类内部访问。

外部不能直接访问

js
class Counter {
  #count = 0
}

const counter = new Counter()

// console.log(counter.#count) // SyntaxError

这和普通属性不同,它不是“约定上的不要访问”,而是语法层面直接禁止。

私有方法

私有的不只是字段,也可以是方法:

js
class User {
  login() {
    return this.#buildToken()
  }

  #buildToken() {
    return 'token'
  }
}

这适合把只服务于类内部的辅助逻辑藏起来。

私有成员和普通属性的区别

普通属性即使以下划线开头,也只是约定:

js
class User {
  constructor() {
    this._name = 'Lee'
  }
}

外部依然可以访问:

js
const user = new User()

console.log(user._name) // 'Lee'

而私有字段不同:

  • 以下划线 _name:约定上的“内部属性”。
  • #name:语言级私有字段。

为什么不直接都用普通属性

普通属性的问题是:

  • 外部可以随意读写。
  • 很难保证内部状态不被误改。
  • 重构时容易被外部调用方绑死。

私有字段更适合表示:

  • 内部计数器
  • 缓存状态
  • 中间计算结果
  • 不希望暴露的实现细节

私有字段不是挂在对象属性表上的普通键

它们不像 this.name 那样是普通属性,因此很多对象操作也拿不到它:

js
class User {
  #name = 'Lee'

  constructor() {
    this.age = 18
  }
}

const user = new User()

console.log(Object.keys(user)) // ['age']

这也是私有字段和普通字段的重要差异。

和继承的关系

私有字段只属于声明它的那个类,本质上不会被子类直接继承访问:

js
class Parent {
  #value = 1

  getValue() {
    return this.#value
  }
}

class Child extends Parent {
  read() {
    // return this.#value // SyntaxError
    return this.getValue()
  }
}

如果子类也需要访问某个状态,通常做法不是直接暴露私有字段,而是:

  • 通过公共方法暴露
  • 通过 getter 暴露只读能力
  • 改为受保护约定

JavaScript 里的“受保护”通常只是约定

很多语言有 protected 关键字,但 JavaScript 原生 class 没有。

实践里常见做法是以下划线表示“类内部和子类可用,但外部不该碰”:

js
class Parent {
  constructor() {
    this._value = 1
  }
}

class Child extends Parent {
  read() {
    return this._value
  }
}

这里的 _value 不是真正受保护,只是团队约定。

什么时候用 #,什么时候用 _

可以这样简单区分:

  • 明确不希望外部和子类直接访问,用 #
  • 只是表达“内部使用,不建议外部碰”,但子类可能会用,用 _

使用建议

  • 需要强约束内部状态时,优先使用私有字段 #
  • 需要让子类复用内部状态时,评估是否改成方法访问或下划线约定。
  • 不要把私有字段当成“语法更酷的普通属性”,它更适合承载真正的内部实现细节。

基于 MIT 许可发布