【class 】static与 # 私有及static私有:系统梳理

写在前面

梳理一下:静态(static)、实例私有(#)与静态私有(static #)。围绕"是什么、为什么、怎么用、适用场景、继承行为、优缺点"等维度记录一下。


概念速览

  • 静态(static) :定义在"类本身"上的属性/方法,不属于实例。访问方式 ClassName.member
  • 实例私有(#) :每个实例独有的"真私有"字段/方法,只能在声明它的类的词法作用域内部访问,语法 this.#field
  • 静态私有(static #) :定义在"类本身且私有"的字段/方法,只能在该类的词法作用域内访问,语法 ClassName.#field(仅类体内可用)。

对照一下:

  • 归属

    • static:类级共享(构造函数对象)
    • #:实例级独有
    • static #:类级共享且私有
  • 可见性

    • static:公有(若不与 # 组合)
    • #:真私有(外部与反射 API 均不可见)
    • static #:真私有(对子类也不可见)
  • 访问语法

    • static:Class.member()/Class.prop
    • #:类内 this.#field
    • static #:类内 Class.#fieldthis.#field(静态上下文)

为什么会出现这些能力(动机?)

  1. 命名空间与组织性:将与实例无关的工具/常量放在类上,避免全局污染与冲突(static)。
  2. 封装与数据隐藏:提供语言层面"真私有"能力,杜绝外部访问与反射绕过(#、static #)。
  3. 性能与语义:实例状态与类级状态分离,减少不必要的实例开销,语义更清晰。
  4. 可维护性:清晰表达"共享状态 vs 实例状态"、"公有 API vs 内部实现"。

用法/示例

静态(static)

js 复制代码
class MathX {
  static PI = 3.14159
  static clamp(v, min, max) {
    return Math.min(Math.max(v, min), max)
  }
}

MathX.PI
MathX.clamp(10, 0, 5) // 5

适合:工具函数、常量集合、静态工厂方法、单例入口(Class.instance())等。

更多示例:

js 复制代码
// 1) 枚举常量与标签化
class HttpStatus {
  static OK = 200
  static NOT_FOUND = 404
  static isOk(code) {
    return code >= 200 && code < 300
  }
}

// 2) 静态工厂 + 轻量缓存(公有 static + 私有实例)
class Color {
  #hex
  constructor(hex) {
    this.#hex = hex
  }
  toString() {
    return this.#hex
  }
  static cache = new Map()
  static fromHex(hex) {
    const key = hex.toLowerCase()
    if (!this.cache.has(key)) {
      this.cache.set(key, new Color(key))
    }
    return this.cache.get(key)
  }
}

const red = Color.fromHex('#ff0000')

实例私有(#)

js 复制代码
class Counter {
  #count = 0
  inc() {
    this.#count++
  }
  value() {
    return this.#count
  }
}

const c = new Counter()
c.inc()
c.value() // 1
// c.#count // SyntaxError:类外不可访问

适合:实例内部状态、对外只暴露受控 API。

更多示例(带私有方法与输入校验):

js 复制代码
class SafeQueue {
  #items = []
  #ensureNotEmpty() {
    if (this.#items.length === 0) throw new Error('Queue is empty')
  }
  enqueue(item) {
    this.#items.push(item)
  }
  dequeue() {
    this.#ensureNotEmpty()
    return this.#items.shift()
  }
  get size() {
    return this.#items.length
  }
}

静态私有(static #)

js 复制代码
class Repo {
  static #cache = new Map()
  static get(id) {
    return this.#cache.get(id)
  }
  static set(id, v) {
    this.#cache.set(id, v)
  }
  static clear() {
    this.#cache.clear()
  }
}

Repo.set('u1', { name: 'Alice' })
Repo.get('u1')
// Repo.#cache // SyntaxError:类外不可访问

适合:类级共享但需要完全私有的数据/逻辑(缓存、注册表、密钥等)。

更多示例(对象池/复用):

js 复制代码
class ConnectionPool {
  static #free = []
  static #busy = new Set()
  static acquire() {
    const conn = this.#free.pop() ?? { id: Math.random().toString(36).slice(2) }
    this.#busy.add(conn)
    return conn
  }
  static release(conn) {
    if (this.#busy.delete(conn)) this.#free.push(conn)
  }
  static stats() {
    return { free: this.#free.length, busy: this.#busy.size }
  }
}

解决了什么问题?(落地场景)

  • 工具/常量归档(static) :如 DateUtils.format()HTTPStatus.OK
  • 单例服务(static + 私有)Logger.getInstance()Config.load();内部用 static # 存储状态。
  • 对象构建的语义化(静态工厂)User.fromJSON(data) 明确输入语义、可返回子类或缓存实例。
  • 强化封装:实例私有状态不可被外部或子类"窥视与破坏"。

继承与多态行为

  • 公有 static 可继承
js 复制代码
class A {
  static x = 1
  static who() {
    return this.name
  }
}
class B extends A {}
B.x // 1
B.who() // 'B',静态方法内 this 指向调用类,支持多态
  • 覆盖(遮蔽)静态成员
js 复制代码
class A {
  static x = 1
}
class B extends A {
  static x = 2
}
A.x // 1; B.x // 2
  • super 调用父类静态
js 复制代码
class A {
  static greet() {
    return 'A'
  }
}
class B extends A {
  static greet() {
    return super.greet() + 'B'
  }
}
  • 私有名不继承#static # 的私有名对外与对子类均不可见,子类若定义同名 #x 也是不同的私有槽。

更多示例(多态静态工厂):

js 复制代码
class Shape {
  static create(data) {
    switch (data.type) {
      case 'circle':
        return new Circle(data.r)
      case 'rect':
        return new Rect(data.w, data.h)
      default:
        throw new Error('unknown type')
    }
  }
}
class Circle extends Shape {
  constructor(r) {
    super()
    this.r = r
  }
}
class Rect extends Shape {
  constructor(w, h) {
    super()
    this.w = w
    this.h = h
  }
}

实际项目中的应用范式

1) 静态工厂 + 实例封装

js 复制代码
class User {
  #name
  #email
  constructor(name, email) {
    this.#name = name
    this.#email = email
  }
  static fromJSON(j) {
    return new User(j.name, j.email)
  }
}

const u = User.fromJSON({ name: 'Alice', email: 'a@x.com' })

2) 单例服务(类级状态 + 私有)

js 复制代码
class Config {
  static #instance
  static #data = new Map()
  static instance() {
    return (this.#instance ??= new Config())
  }
  static set(k, v) {
    this.#data.set(k, v)
  }
  static get(k) {
    return this.#data.get(k)
  }
}

Config.set('env', 'prod')
Config.get('env')

3) 缓存/注册表(静态私有保护内部结构)

js 复制代码
class RouterRegistry {
  static #routes = new Map()
  static register(name, handler) {
    this.#routes.set(name, handler)
  }
  static resolve(name) {
    return this.#routes.get(name)
  }
}

4) 组合项目实践(与实际代码契合)

在复杂表单/查询组件中:

  • 使用 实例私有 # 保存每个组件实例的临时状态(如输入缓冲、计时器句柄)。
  • 使用 静态或静态私有 保存跨实例共享的缓存(如下拉备选项缓存/词典),并通过公有静态方法暴露受控访问。

5) Facade 门面 + 限流埋点

js 复制代码
class HttpFacade {
  static #lastAt = 0
  static #minIntervalMs = 200
  static async get(url) {
    const now = Date.now()
    if (now - this.#lastAt < this.#minIntervalMs) {
      await new Promise((r) => setTimeout(r, this.#minIntervalMs - (now - this.#lastAt)))
    }
    this.#lastAt = Date.now()
    // 这里可统一加鉴权头/埋点
    return fetch(url).then((r) => r.json())
  }
}

6) 词典缓存(跨组件共享)

ts 复制代码
class DictCache {
  static #map = new Map<string, any[]>()
  static async get(name: string, loader: () => Promise<any[]>) {
    if (!this.#map.has(name)) this.#map.set(name, await loader())
    return this.#map.get(name)!
  }
}
// 组件中:await DictCache.get('country', loadCountry)

优缺点

静态(static)

  • 优点:组织清晰、节省实例内存、天然单例入口、易被 IDE/TS 分析。
  • 缺点:过多静态全局化可能导致隐式依赖、测试隔离变难(需小心设计 API)。

实例私有(#)

  • 优点:强封装,无法被外部或反射访问;语义明确,避免 API 泄漏。
  • 缺点:调试与序列化不直观;与旧环境/装饰器生态的互操作性需注意。

静态私有(static #)

  • 优点:类级共享同时保持完全私有;非常适合缓存、密钥、内部注册表。
  • 缺点:对子类不可见,不能复用内部实现;重构时需要明确边界与对外 API。

得清楚

  1. 分清状态归属 :实例状态用 #,跨实例共享用 static;需要隐藏则用 static #
  2. 最小可见原则:默认私有,按需暴露公有静态方法作为网关。
  3. 控制全局性:对静态成员的修改要可追踪(记录、事件或只读常量)。
  4. 工厂优先 :涉及复杂创建流程时,优先静态工厂(fromX/of/create)。
  5. 测试友好:通过参数注入或静态"访问器"留出可替换点,避免单例硬耦合。

常见误区

  • 认为 static 就能从实例上访问:错误,实例上访问不到静态成员。
  • 以为 # 与私有约定(下划线)等价:不等价,# 是语法层面的强封装。
  • 在子类中使用父类的 #:不可行,私有名不向子类开放。
  • 滥用静态全局状态:导致可维护性与测试隔离性变差。

进阶:class 高级用法与关键细节

静态初始化块(static { })

js 复制代码
class Registry {
  static #map
  static {
    // 初始化顺序:先字段初始值,再 static 块
    this.#map = new Map()
    // 可执行一次性注册/校验逻辑
  }
  static get(key) {
    return this.#map.get(key)
  }
}

适合一次性"装配"工作:常量校验、跨静态字段的依赖初始化、环境探测等。

更多示例(环境守卫与预热):

js 复制代码
class Feature {
  static #enabled = false
  static {
    const env = (typeof process !== 'undefined' && process.env && process.env.NODE_ENV) || 'prod'
    this.#enabled = env !== 'test'
    // 预热只在非测试环境执行
    if (this.#enabled) {
      // do warmup
    }
  }
  static enabled() {
    return this.#enabled
  }
}

私有方法、访问器、以及静态私有方法

js 复制代码
class Secure {
  #secret = 'k'
  #calc(x) {
    return x + this.#secret
  }
  get value() {
    return this.#calc('v')
  }
  static #hash(s) {
    return `#${s}`
  }
  static encode(s) {
    return this.#hash(s)
  }
}

建议:

  • 将实现细节下沉为私有方法;对外仅暴露极简、公有稳定 API。
  • 静态私有方法适合封装类级算法(如序列号计算、缓存键生成)。

更多示例(键生成与签名):

js 复制代码
class Signer {
  static #salt = 's'
  static #key(str) {
    return `${str}:${this.#salt}`
  }
  static sign(str) {
    return btoa(this.#key(str))
  }
}

私有名"品牌检查"(brand check)

js 复制代码
class A {
  #x = 1
  static isA(obj) {
    try {
      return #x in obj
    } catch {
      return false
    }
  }
}

说明:#x in obj 只能在声明该私有名的类词法作用域内使用,用于更"强"的类型守卫。

更多示例(跨模块安全检查):

js 复制代码
class Token {
  #v = 1
  static isToken(x) {
    try {
      return #v in x
    } catch {
      return false
    }
  }
}

迭代协议与符号方法

js 复制代码
class Range {
  constructor(start, end) {
    this.start = start
    this.end = end
  }
  [Symbol.iterator]() {
    let cur = this.start
    const end = this.end
    return {
      next: () => (cur <= end ? { value: cur++, done: false } : { value: undefined, done: true })
    }
  }
  get [Symbol.toStringTag]() {
    return 'Range'
  }
}

用途:让类可用于 for...of... 展开;自定义 Object.prototype.toString.call() 的标识。

TS 特性:抽象类、接口实现、构造函数重载

ts 复制代码
abstract class RepoBase<T> {
  abstract find(id: string): Promise<T | undefined>
}
interface User {
  id: string
  name: string
}
class UserRepo extends RepoBase<User> {
  async find(id: string) {
    /* ... */ return undefined
  }
}

class Point {
  // 重载签名(TS):
  constructor(x: number, y: number)
  constructor(p: { x: number; y: number })
  constructor(a: number | { x: number; y: number }, b?: number) {
    /* ... */
  }
}

建议:用抽象类表达"可部分复用的骨架",用接口约束外部契约;构造重载表达输入形态。

更多示例(泛型仓库 + 静态工厂):

ts 复制代码
interface Entity {
  id: string
}
abstract class RepoBase2<T extends Entity> {
  static fromEnv<TRepo extends RepoBase2<any>>(ctor: new () => TRepo): TRepo {
    return new ctor()
  }
  abstract all(): Promise<T[]>
}
class MemoryRepo<T extends Entity> extends RepoBase2<T> {
  #items: T[] = []
  async all() {
    return this.#items
  }
}
const repo = RepoBase2.fromEnv(MemoryRepo)

mixin 与组合优先

  • 避免深继承;将横切能力做成 mixin/组合函数,注入到具体类中。
  • 在 Vue/React 等项目中,优先"组合函数 + 类"而非层层继承。

构造阶段的健壮性

  • 使用 new.target 限制直接实例化抽象基类。
  • 在构造函数中校验参数,保持类实例的"不变量"。
  • 通过参数注入(依赖注入)提高可测试性。

与模块的取舍(静态类 vs 模块函数)

  • 若功能仅是"无状态工具",可优先模块函数;需要封装状态/生命周期时再用类。
  • 需要封装"私有共享状态"且暴露受控入口时,使用"静态 + 静态私有"更合适。

为什么要用 class 构造函数

  • 表达不变量与初始化协议 :在 constructor 中集中做参数校验、默认值设置、依赖注入,保证对象一创建即有效。
  • 类型友好(TS) :支持构造参数重载、参数属性(constructor(public id: string))简化声明;有利于 IDE 推断与重构。
  • 可测试/可替换:通过注入接口实现或 mock,解耦外部资源(HTTP、缓存、日志)。
  • 生命周期挂钩 :构造时注册、销毁时清理(结合显式 dispose)。
  • 语义聚合:相关状态与行为集中在同一类实例上,避免散落函数造成的上下文丢失。

构造函数典型场景

  • 实例有"身份/状态"的领域对象:UserOrderCartFormController
  • 需要维护跨多次调用的"会话性状态":编辑器、下载器、数据拉取器(含取消/重试)。
  • 需要多态的场景:策略对象、存储驱动、渲染后端(基类 + 多实现)。

例(数据拉取器,含取消/重试):

js 复制代码
class Fetcher {
  #controller = null
  #retries = 0
  #maxRetries = 2
  async get(url) {
    this.cancel()
    this.#controller = new AbortController()
    try {
      const res = await fetch(url, { signal: this.#controller.signal })
      return await res.json()
    } catch (e) {
      if (this.#retries++ < this.#maxRetries) return this.get(url)
      throw e
    } finally {
      this.#controller = null
      this.#retries = 0
    }
  }
  cancel() {
    this.#controller?.abort()
  }
}

为什么要用"静态类"(仅静态成员的类)

说明:JS 无 static class 关键字,这里指"仅包含静态成员的类"。

  • 集中入口:将相关工具/工厂/注册表置于同一命名空间,避免散乱导出。
  • 私有共享状态 :用 static # 存储缓存/注册表/密钥,外部不可见、子类也不可见。
  • 可替换点:在测试中可整体替换或 monkey-patch 公有静态方法。
  • 跨实例共享:避免为每个实例重复创建相同数据/连接(谨慎使用)。

静态类典型场景

  • 工具/常量聚合:DateUtilsMathX、HTTP 状态常量。
  • 注册表/缓存:RouterRegistryModelCache(内部 static #map)。
  • 工厂/单例:Logger.getInstance()Config.instance();静态方法返回实例。
  • 受控网关:对底层库/系统能力的门面(Facade),统一鉴权/限流/埋点。

例(Logger 单例 + 分级输出):

js 复制代码
class Logger {
  static #instance
  static #level = 'info'
  static getInstance() {
    return (this.#instance ??= new Logger())
  }
  static setLevel(lv) {
    this.#level = lv
  }
  info(...args) {
    if (Logger.#level === 'info') console.log('[INFO]', ...args)
  }
  warn(...args) {
    console.warn('[WARN]', ...args)
  }
}

const log = Logger.getInstance()
log.info('ready')

构造函数 vs 静态类:应用场景对照

  • 有状态对象(选 构造函数):需要多实例、持久状态、策略多态、与 UI/生命周期绑定。
  • 无状态或类级状态(选 静态类):工具函数、常量、注册表、缓存、单例入口。
  • 组合式设计:用"构造函数类"承载实例语义;用"静态类"提供工厂/缓存/门面。
  • 演进路径:先模块函数 → 发现需要隐藏共享状态 → 升级为"静态类 + static #"。

兼容性与工程注意事项

  • 转译与目标环境#/static # 在旧环境需经 Babel/TS 转译;注意输出目标(如 ES2019+)。
  • 调试与序列化 :私有字段在 DevTools 不直观;通过显式 toJSON 控制序列化。
  • 测试隔离 :修改静态状态后要在 afterEach 清理;或提供 reset/clear 静态方法。
  • 性能权衡:避免在构造函数做重活(I/O),延迟到方法调用或使用静态缓存。
  • 最小暴露:默认私有/内部,仅暴露必要的公有 API 与类型。

测试隔离示例:

js 复制代码
class CacheX {
  static #m = new Map()
  static set(k, v) {
    this.#m.set(k, v)
  }
  static get(k) {
    return this.#m.get(k)
  }
  static clear() {
    this.#m.clear()
  }
}

// test
afterEach(() => CacheX.clear())

总结

  • static:类级共享,适合工具/常量/工厂/单例入口。
  • #(实例私有):实例内部状态的强封装。
  • static #(静态私有):类级共享但严格私有的实现细节与数据。

选择关键:先判断"状态归属"(实例 vs 类),再决定"可见性"(公有 vs 私有),最后以最小暴露面提供稳定 API。

相关推荐
Beginner x_u2 小时前
前端八股文 Vue上
前端·javascript·vue.js·八股文
江拥羡橙2 小时前
JavaScript异步编程:告别回调地狱,拥抱Promise async/await
开发语言·javascript·ecmascript·promise·async/await
用泥种荷花2 小时前
【web音频学习(七)】科大讯飞Web端语音合成
前端
云枫晖2 小时前
JS核心知识-对象继承
前端·javascript
w重名了1098822 小时前
记录一次gnvm切换node版本显示内存溢出的报错
前端·node.js
我是天龙_绍2 小时前
经常写CSS难的复杂的就害怕,不用怕,谈 渐变 不色变
前端
用户2519162427112 小时前
Node之EventEmitter
前端·javascript·node.js
鹏多多2 小时前
flutter-详解控制组件显示的两种方式Offstage与Visibility
前端·flutter
用户7236237370582 小时前
农业银行转账模拟器, 银行卡余额模拟器,jar最新逆向插件开源
前端