掘金文章归档 · 第 37 篇 JS 进阶系列 2/8 Class · 静态/私有

js Class 中静态属性和私有属性使用场景的区别

static vs # 前缀:核心区别 + 场景对比 + 实战案例

原文作者:随意_(掘金) | juejin.cn/post/7579982168452546611 | 发布于 2025-12-05 | 阅读 297 · 约 4 分钟

1一、核心区别(一张表看懂)

静态属性/方法(static)和私有属性/方法(# 前缀)是 Class 语法中两个核心的封装特性,但设计目标、访问范围、使用场景完全不同。

维度静态属性/方法(static)私有属性/方法(# 前缀)
归属属于"类本身",而非实例(所有实例共享同一静态成员)属于"实例",但仅能在类内部访问(每个实例有独立的私有成员)
访问范围类外部可通过 类名.静态成员 访问,实例无法访问仅类内部可访问,类外部/实例都无法直接访问
设计目标复用"不依赖实例状态"的通用逻辑/常量隐藏"内部实现细节",避免外部篡改/误操作
继承性子类可通过 子类名.静态成员 继承(可重写)私有成员不能被继承(子类无法访问父类的 # 成员)
内存占用全局唯一(类加载时初始化,仅占一份内存)每个实例独立占用内存(实例创建时初始化)

2二、静态属性/方法(static):类级别的通用能力

核心理解:static 修饰的成员属于"类",不是"实例"——你可以把类想象成一个"工具库",静态成员就是工具库里直接能用的工具,不需要先"打开工具库(实例化)"。

场景 1:工具函数/常量(无需实例化,直接调用)

适用于"不依赖实例状态"的通用逻辑,比如数学计算、日期格式化、常量定义。

class TimeUtil {
  // 静态常量:通用配置
  static ONE_DAY = 24 * 60 * 60 * 1000; // 一天的毫秒数
  
  // 静态方法:日期格式化(不依赖任何实例属性)
  static formatDate(date, format = 'YYYY-MM-DD') {
    const d = new Date(date);
    return format
      .replace('YYYY', d.getFullYear())
      .replace('MM', String(d.getMonth() + 1).padStart(2, '0'))
      .replace('DD', String(d.getDate()).padStart(2, '0'));
  }
}

// 直接用类名调用,无需 new
console.log(TimeUtil.ONE_DAY); // 86400000
console.log(TimeUtil.formatDate(new Date())); // 2025-12-05

// 实例无法访问静态成员
const util = new TimeUtil();
console.log(util.ONE_DAY); // undefined

场景 2:单例模式(限制类只能实例化一次)

通过静态方法控制实例的创建,确保全局只有一个实例(如全局状态管理、弹窗管理器)。

class ModalManager {
  static instance; // 静态属性:存储唯一实例
  constructor() {
    this.modals = []; // 实例属性:管理所有弹窗
  }

  // 静态方法:获取唯一实例
  static getInstance() {
    if (!ModalManager.instance) {
      ModalManager.instance = new ModalManager();
    }
    return ModalManager.instance;
  }

  // 实例方法:添加弹窗
  addModal(modal) {
    this.modals.push(modal);
  }
}

// 两次调用都返回同一个实例
const manager1 = ModalManager.getInstance();
const manager2 = ModalManager.getInstance();
console.log(manager1 === manager2); // true

场景 3:工厂方法(统一创建实例)

用静态方法封装实例化逻辑,对外提供更友好的创建接口(比如参数校验、默认值处理)。

class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 静态工厂方法:创建合法的用户实例
  static createUser(userInfo) {
    // 前置校验:避免创建非法实例
    if (!userInfo.name || userInfo.age < 0) {
      throw new Error('用户名不能为空,年龄不能为负数');
    }
    return new User(userInfo.name, userInfo.age);
  }
}

// 通过工厂方法创建实例(无需关心内部校验逻辑)
const user = User.createUser({ name: '张三', age: 20 });
// 错误示例:会抛出异常
// const invalidUser = User.createUser({ name: '', age: -1 });

3三、私有属性/方法(# 前缀):实例级别的隐私保护

核心理解:# 修饰的成员属于"实例",但被严格隔离在类内部——就像手机的内部零件,用户只能用手机的功能(公开方法),但不能直接拆零件(私有成员),避免外部篡改导致逻辑出错。

场景 1:保护核心状态(避免外部随意修改)

比如用户的密码、订单的状态码,只能通过类的公开方法修改,确保修改逻辑可控。

class Order {
  #status; // 私有属性:订单状态(0-待支付,1-已支付,2-已取消)
  constructor(orderId) {
    this.orderId = orderId;
    this.#status = 0; // 初始状态:待支付
  }

  // 公开方法:修改状态(带校验逻辑)
  pay() {
    if (this.#status === 0) {
      this.#status = 1;
      console.log(`订单${this.orderId}支付成功`);
    } else {
      console.log(`订单${this.orderId}状态异常,无法支付`);
    }
  }

  // 公开方法:获取状态(只读,不暴露原始值)
  getStatusText() {
    const statusMap = { 0: '待支付', 1: '已支付', 2: '已取消' };
    return statusMap[this.#status];
  }
}

const order = new Order('123456');
order.pay(); // 订单123456支付成功
console.log(order.getStatusText()); // 已支付

// 外部无法直接修改私有属性(报错)
// order.#status = 2; // SyntaxError

场景 2:封装内部辅助逻辑(避免暴露无关方法)

类的内部计算、校验等辅助逻辑不需要对外暴露,用私有方法封装,让公开接口更简洁。

class FormValidator {
  constructor(formData) {
    this.formData = formData;
  }

  // 公开方法:对外提供的核心能力
  validate() {
    return this.#validateRequired() && this.#validateFormat();
  }

  // 私有方法:校验必填项(内部逻辑,外部无需关心)
  #validateRequired() {
    const requiredFields = ['username', 'phone'];
    return requiredFields.every(field => this.formData[field]);
  }

  // 私有方法:校验格式(内部逻辑)
  #validateFormat() {
    const phoneReg = /^1[3-9]\d{9}$/;
    return phoneReg.test(this.formData.phone);
  }
}

const validator = new FormValidator({ username: '张三', phone: '13800138000' });
console.log(validator.validate()); // true

// 外部无法调用私有方法(报错)
// validator.#validateRequired(); // SyntaxError

场景 3:避免命名冲突(多人协作/继承场景)

私有成员不会被外部/子类覆盖,确保类的内部逻辑稳定。

class Parent {
  #commonMethod() {
    console.log('父类私有方法');
  }

  callPrivate() {
    this.#commonMethod(); // 调用父类私有方法
  }
}

class Child extends Parent {
  // 子类可以定义同名的私有方法,不会和父类冲突
  #commonMethod() {
    console.log('子类私有方法');
  }

  callChildPrivate() {
    this.#commonMethod(); // 调用子类私有方法
  }
}

const child = new Child();
child.callPrivate(); // 父类私有方法
child.callChildPrivate(); // 子类私有方法

4四、核心总结:怎么选?

需求场景选 static选 # 私有
不需要实例化,直接用的工具/常量
控制实例的创建逻辑(工厂/单例)
保护实例的核心状态,避免外部篡改
封装内部辅助逻辑,简化公开接口
逻辑不依赖实例状态(无 this)
逻辑依赖实例状态,但需隐藏
📌 一句话记忆:static 给"类"加工具,所有实例共享,外部可访问;# 私有给"实例"加隐私,仅类内部可用,外部碰不到。

实战小技巧

结合案例

class UserService {
  static #API_BASE = '/api/user'; // 静态私有常量:接口地址(类级别的隐私)
  
  // 静态方法:调用用户接口(工具能力 + 私有常量)
  static async getUserInfo(userId) {
    const res = await fetch(`${this.#API_BASE}/${userId}`);
    return res.json();
  }
}

// 调用静态方法,无需实例化,且接口地址被私有保护
UserService.getUserInfo(123);