主题
私有属性和方法
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 不是真正受保护,只是团队约定。
什么时候用 #,什么时候用 _
可以这样简单区分:
- 明确不希望外部和子类直接访问,用
#。 - 只是表达“内部使用,不建议外部碰”,但子类可能会用,用
_。
使用建议
- 需要强约束内部状态时,优先使用私有字段
#。 - 需要让子类复用内部状态时,评估是否改成方法访问或下划线约定。
- 不要把私有字段当成“语法更酷的普通属性”,它更适合承载真正的内部实现细节。
