前端 Class 语法从 0 开始学起

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 推荐用函数式
有状态 + 行为 ✅(如弹窗、购物车)
无状态工具函数 ❌(除非需要静态方法封装) ✅(如单独的格式化函数)
需要继承 / 复用 ✅(如组件扩展) ❌(函数式靠组合)
简单逻辑(一行代码)
需封装私有逻辑 ✅(# 私有成员) ❌(函数式靠闭包,较繁琐)

五、常见坑点

  1. this 指向问题:类的方法中 this 默认指向实例,但单独提取方法时会丢失 this:

    javascript

    运行

    arduino 复制代码
    class 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); }
  2. 私有成员必须先声明#password 必须在类顶部声明,不能直接在 constructor 里写 this.#password = xxx(未声明会报错)。

  3. super 必须先调用 :子类 constructor 中,必须先调用 super() 才能使用 this

  4. Class 不能提升:函数声明可以提升(先调用后声明),但 Class 不行,必须先声明再实例化。

六、总结

Class 不是「高级语法」,而是「更优雅的原型链封装」,核心价值是:

  • 封装:把数据和操作数据的方法放在一起,隐藏内部细节(私有成员)。
  • 复用:通过继承复用通用逻辑,减少重复代码。
  • 清晰:结构符合面向对象思维,易读易维护。
相关推荐
前端婴幼儿1 小时前
前端直接下载到本地(实时显示下载进度)
前端
hjt_未来可期1 小时前
js实现复制、粘贴文字
前端·javascript·html
小明记账簿_微信小程序1 小时前
webpack实用配置dev--react(一)
前端
ohyeah1 小时前
AI First 时代:用大模型构建轻量级后台管理系统
前端·llm
Apeng_09191 小时前
vue实现页面不断插入内容并且自动滚动功能
前端·javascript·vue.js
孟祥_成都2 小时前
Prompt 还能哄女朋友!你真的知道如何问 ai 问题吗?
前端·人工智能
前端涂涂2 小时前
第3讲:BTC-数据结构
前端
白狐_7982 小时前
【项目实战】我用一个 HTML 文件写了一个“CET-6 单词斩”
前端·算法·html
夕水2 小时前
React Server Components 中的严重安全漏洞
前端