Class 是 ES6 引入的面向对象编程(OOP)语法糖,本质是对 JavaScript 原型链的封装,让代码结构更清晰、易维护,尤其适合组件封装、工具类开发、状态管理等场景。下面从「基础语法→核心特性→实战场景→最佳实践」一步步拆解。
一、为什么要学 Class?
在 ES6 之前,JavaScript 靠「构造函数 + 原型」实现面向对象,代码冗余且不直观:
javascript
运行
javascript
// 传统构造函数方式
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log(`Hi, 我是${this.name}`);
};
const p1 = new Person('张三', 20);
而 Class 语法让代码更接近传统面向对象语言(如 Java/TS),结构更清晰:
javascript
运行
javascript
// Class 语法(等价于上面的代码)
class Person {
constructor(name, age) {
this.name = name; // 实例属性
this.age = age;
}
sayHi() { // 原型方法
console.log(`Hi, 我是${this.name}`);
}
}
const p1 = new Person('张三', 20);
二、Class 核心基础语法
1. 基本结构:constructor + 方法
constructor:构造函数,创建实例时自动执行,用于初始化实例属性- 方法:直接写在 Class 内部,会挂载到原型上(所有实例共享)
- 实例化:必须用
new关键字(普通函数可直接调用,Class 不行)
javascript
运行
javascript
// 完整基础示例
class Car {
// 1. 构造函数:初始化实例属性
constructor(brand, price) {
this.brand = brand; // 实例私有属性(每个实例独立)
this.price = price;
}
// 2. 原型方法:所有实例共享
start() {
console.log(`${this.brand}启动了,价格${this.price}元`);
}
// 3. 普通方法也可以接收参数
discount(rate) {
this.price = this.price * rate;
console.log(`折扣后价格:${this.price}元`);
}
}
// 实例化
const bmw = new Car('宝马', 500000);
bmw.start(); // 宝马启动了,价格500000元
bmw.discount(0.8); // 折扣后价格:400000元
const benz = new Car('奔驰', 600000);
benz.start(); // 奔驰启动了,价格600000元
2. 关键特性:静态属性 / 方法、私有属性 / 方法
(1)静态属性 / 方法(static)
- 属于「类本身」,而非实例,通过
类名.xxx调用 - 常用场景:工具方法、常量、不依赖实例的逻辑
javascript
运行
javascript
class MathUtil {
// 静态属性:常量
static PI = 3.1415926;
// 静态方法:工具函数(无需实例化就能用)
static circleArea(r) {
return this.PI * r * r; // 静态方法内的this指向类本身
}
}
// 直接通过类名调用,无需new
console.log(MathUtil.PI); // 3.1415926
console.log(MathUtil.circleArea(10)); // 314.15926
// 实例无法访问静态成员
const util = new MathUtil();
console.log(util.PI); // undefined
(2)私有属性 / 方法(# 前缀)
- ES2022 新增,只能在类内部访问,外部无法修改 / 调用
- 常用场景:封装内部逻辑,避免外部篡改
javascript
运行
javascript
class User {
// 私有属性:必须用#声明
#password; // 先声明
constructor(username, password) {
this.username = username;
this.#password = password; // 赋值
}
// 私有方法
#checkPassword() {
return this.#password.length >= 6;
}
// 公开方法:间接访问私有成员
login() {
if (this.#checkPassword()) {
console.log(`${this.username}登录成功`);
} else {
console.log('密码长度不足6位');
}
}
}
const user1 = new User('zhangsan', '123456');
user1.login(); // zhangsan登录成功
// 外部无法访问私有成员
console.log(user1.#password); // 报错!
user1.#checkPassword(); // 报错!
3. 继承(extends + super)
- 用
extends实现类的继承,子类可复用父类的属性和方法 super:调用父类的构造函数 / 方法- 常用场景:组件复用(如 React 组件)、通用逻辑扩展
javascript
运行
javascript
// 父类:通用的动物属性
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}在吃东西`);
}
}
// 子类:继承父类,扩展特有逻辑
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用super,才能用this
this.breed = breed; // 子类特有属性
}
// 重写父类方法
eat() {
super.eat(); // 调用父类的eat方法
console.log(`${this.name}(${this.breed})喜欢吃肉`);
}
// 子类特有方法
bark() {
console.log(`${this.name}汪汪叫`);
}
}
const erha = new Dog('二哈', '哈士奇');
erha.eat();
// 二哈在吃东西
// 二哈(哈士奇)喜欢吃肉
erha.bark(); // 二哈汪汪叫
三、Class 的核心使用场景(附实战案例)
场景 1:前端组件封装(最常用)
前端框架(Vue/React)中,Class 常用于封装可复用的组件,比如弹窗、表单、轮播图。
案例:封装一个可复用的弹窗组件
javascript
运行
kotlin
class Modal {
// 私有属性:弹窗DOM元素
#modalEl;
constructor(options = {}) {
// 默认配置 + 用户配置
this.config = {
title: '提示',
content: '',
isShow: false,
...options
};
this.#createModal(); // 初始化创建DOM
}
// 私有方法:创建弹窗DOM
#createModal() {
this.#modalEl = document.createElement('div');
this.#modalEl.className = 'modal';
this.#modalEl.innerHTML = `
<div class="modal-header">${this.config.title}</div>
<div class="modal-content">${this.config.content}</div>
<button class="modal-close">关闭</button>
`;
document.body.appendChild(this.#modalEl);
this.#bindEvent(); // 绑定事件
this.#updateShowStatus(); // 初始化显示状态
}
// 私有方法:绑定事件
#bindEvent() {
const closeBtn = this.#modalEl.querySelector('.modal-close');
closeBtn.addEventListener('click', () => {
this.hide();
});
}
// 公开方法:显示弹窗
show() {
this.config.isShow = true;
this.#updateShowStatus();
}
// 公开方法:隐藏弹窗
hide() {
this.config.isShow = false;
this.#updateShowStatus();
}
// 私有方法:更新显示状态
#updateShowStatus() {
this.#modalEl.style.display = this.config.isShow ? 'block' : 'none';
}
// 公开方法:销毁弹窗
destroy() {
document.body.removeChild(this.#modalEl);
}
}
// 使用弹窗
const loginModal = new Modal({
title: '登录提示',
content: '请先登录再操作'
});
// 按钮触发显示
document.querySelector('#showModal').addEventListener('click', () => {
loginModal.show();
});
何时用:需要封装「有状态、有方法、可复用」的 UI 组件时(如弹窗、表单、轮播),Class 能把「数据(状态)+ 行为(方法)+ DOM 操作」封装在一起,比零散的函数更易维护。
场景 2:工具类封装
把通用的工具函数封装成 Class,方便管理和扩展。
案例:日期处理工具类
javascript
运行
javascript
class DateUtil {
// 静态方法:格式化日期
static format(date, format = 'YYYY-MM-DD HH:mm:ss') {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hour = String(d.getHours()).padStart(2, '0');
const minute = String(d.getMinutes()).padStart(2, '0');
const second = String(d.getSeconds()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hour)
.replace('mm', minute)
.replace('ss', second);
}
// 静态方法:计算两个日期的差值(天)
static diffDays(date1, date2) {
const time1 = new Date(date1).getTime();
const time2 = new Date(date2).getTime();
return Math.abs(Math.floor((time1 - time2) / (1000 * 60 * 60 * 24)));
}
}
// 使用
console.log(DateUtil.format(new Date())); // 2025-12-05 10:30:00
console.log(DateUtil.diffDays('2025-12-01', '2025-12-05')); // 4
何时用:当工具函数有「关联性」(如都是日期处理、都是数学计算),用 Class 封装成静态方法,比零散的函数更易查找和维护(避免全局函数污染)。
场景 3:状态管理(小型应用)
在没有 Vuex/Pinia 的小型项目中,用 Class 封装状态和状态修改逻辑。
案例:购物车状态管理
javascript
运行
javascript
class CartStore {
// 私有属性:购物车数据
#items = [];
// 公开方法:获取购物车列表
getItems() {
return [...this.#items]; // 返回副本,避免外部修改
}
// 公开方法:添加商品
addItem(goods) {
const existItem = this.#items.find(item => item.id === goods.id);
if (existItem) {
existItem.quantity += 1;
} else {
this.#items.push({ ...goods, quantity: 1 });
}
this.#updateTotal();
}
// 公开方法:删除商品
removeItem(id) {
this.#items = this.#items.filter(item => item.id !== id);
this.#updateTotal();
}
// 私有方法:更新总价
#updateTotal() {
this.total = this.#items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}
// 全局实例(单例)
const cart = new CartStore();
// 使用
cart.addItem({ id: 1, name: '手机', price: 2000 });
cart.addItem({ id: 1, name: '手机', price: 2000 }); // 数量变为2
console.log(cart.getItems()); // [{ id:1, name:'手机', price:2000, quantity:2 }]
console.log(cart.total); // 4000
何时用:小型项目中,不需要复杂的状态管理库时,用 Class 封装「状态 + 修改逻辑」,保证状态的修改可追踪、可控制。
四、Class vs 函数式:何时该用 Class?
| 场景 | 推荐用 Class | 推荐用函数式 |
|---|---|---|
| 有状态 + 行为 | ✅(如弹窗、购物车) | ❌ |
| 无状态工具函数 | ❌(除非需要静态方法封装) | ✅(如单独的格式化函数) |
| 需要继承 / 复用 | ✅(如组件扩展) | ❌(函数式靠组合) |
| 简单逻辑(一行代码) | ❌ | ✅ |
| 需封装私有逻辑 | ✅(# 私有成员) | ❌(函数式靠闭包,较繁琐) |
五、常见坑点
-
this 指向问题:类的方法中 this 默认指向实例,但单独提取方法时会丢失 this:
javascript
运行
arduinoclass Button { constructor(text) { this.text = text; } click() { console.log(this.text); } } const btn = new Button('提交'); // 错误:this 变为 undefined document.querySelector('button').addEventListener('click', btn.click); // 解决:绑定this document.querySelector('button').addEventListener('click', btn.click.bind(btn)); // 或用箭头函数 click = () => { console.log(this.text); } -
私有成员必须先声明 :
#password必须在类顶部声明,不能直接在 constructor 里写this.#password = xxx(未声明会报错)。 -
super 必须先调用 :子类 constructor 中,必须先调用
super()才能使用this。 -
Class 不能提升:函数声明可以提升(先调用后声明),但 Class 不行,必须先声明再实例化。
六、总结
Class 不是「高级语法」,而是「更优雅的原型链封装」,核心价值是:
- 封装:把数据和操作数据的方法放在一起,隐藏内部细节(私有成员)。
- 复用:通过继承复用通用逻辑,减少重复代码。
- 清晰:结构符合面向对象思维,易读易维护。