静态属性 / 方法(static)和私有属性 / 方法(# 前缀)是 Class 语法中两个核心的封装特性,但设计目标、访问范围、使用场景完全不同。下面用「核心区别 + 场景对比 + 实战案例」的方式讲清楚,帮你彻底区分。
一、核心区别(一张表看懂)
| 维度 | 静态属性 / 方法(static) | 私有属性 / 方法(# 前缀) |
|---|---|---|
| 归属 | 属于「类本身」,而非实例(所有实例共享同一静态成员) | 属于「实例」,但仅能在类内部访问(每个实例有独立的私有成员) |
| 访问范围 | 类外部可通过 类名.静态成员 访问,实例无法访问 |
仅类内部可访问,类外部 / 实例都无法直接访问 |
| 设计目标 | 复用「不依赖实例状态」的通用逻辑 / 常量 | 隐藏「内部实现细节」,避免外部篡改 / 误操作 |
| 继承性 | 子类可通过 子类名.静态成员 继承(可重写) |
私有成员不能被继承(子类无法访问父类的 # 成员) |
| 内存占用 | 全局唯一(类加载时初始化,仅占一份内存) | 每个实例独立占用内存(实例创建时初始化) |
二、静态属性 / 方法(static):类级别的通用能力
核心理解
static 修饰的成员属于「类」,不是「实例」------ 你可以把类想象成一个「工具库」,静态成员就是工具库里直接能用的工具,不需要先 "打开工具库(实例化)"。
典型使用场景
场景 1:工具函数 / 常量(无需实例化,直接调用)
适用于「不依赖实例状态」的通用逻辑,比如数学计算、日期格式化、常量定义。
典型使用场景
场景 1:工具函数 / 常量(无需实例化,直接调用)
适用于「不依赖实例状态」的通用逻辑,比如数学计算、日期格式化、常量定义。
javascript
运行
javascript
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:单例模式(限制类只能实例化一次)
通过静态方法控制实例的创建,确保全局只有一个实例(如全局状态管理、弹窗管理器)。
javascript
运行
javascript
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:工厂方法(统一创建实例)
用静态方法封装实例化逻辑,对外提供更友好的创建接口(比如参数校验、默认值处理)。
javascript
运行
javascript
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 });
三、私有属性 / 方法(# 前缀):实例级别的隐私保护
核心理解
# 修饰的成员属于「实例」,但被严格隔离在类内部 ------ 就像手机的内部零件,用户只能用手机的功能(公开方法),但不能直接拆零件(私有成员),避免外部篡改导致逻辑出错。
典型使用场景
场景 1:保护核心状态(避免外部随意修改)
比如用户的密码、订单的状态码,只能通过类的公开方法修改,确保修改逻辑可控。
javascript
运行
javascript
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:封装内部辅助逻辑(避免暴露无关方法)
类的内部计算、校验等辅助逻辑,不需要对外暴露,用私有方法封装,让公开接口更简洁。
javascript
运行
javascript
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:避免命名冲突(多人协作 / 继承场景)
私有成员不会被外部 / 子类覆盖,确保类的内部逻辑稳定。
javascript
运行
javascript
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(); // 子类私有方法
四、核心总结:怎么选?
| 需求场景 | 选 static | 选 # 私有 |
|---|---|---|
| 不需要实例化,直接用的工具 / 常量 | ✅ | ❌ |
| 控制实例的创建逻辑(工厂 / 单例) | ✅ | ❌ |
| 保护实例的核心状态,避免外部篡改 | ❌ | ✅ |
| 封装内部辅助逻辑,简化公开接口 | ❌ | ✅ |
| 逻辑不依赖实例状态(无 this) | ✅ | ❌ |
| 逻辑依赖实例状态,但需隐藏 | ❌ | ✅ |
一句话记忆
static:给「类」加工具,所有实例共享,外部可访问;#私有:给「实例」加隐私,仅类内部可用,外部碰不到。
实战小技巧
- 如果你写的逻辑「不需要 new 就能用」,优先用 static;
- 如果你写的逻辑「需要保护,不让外部改」,优先用 # 私有;
- 大部分业务场景中,static 用于「通用工具」,# 私有用于「实例隐私」,两者也可结合(比如静态方法调用私有方法,但需先创建实例)。
比如结合案例:
javascript
运行
javascript
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);