跳到主要内容

JavaScript ES6类

引言

在 JavaScript ES6(ECMAScript 2015)之前,JavaScript 是通过原型继承来实现面向对象编程的,这种方式对于熟悉传统面向对象语言的开发者来说可能不太直观。ES6 引入的类(Class)语法提供了一种更清晰、更简洁的方式来创建对象和处理继承,使 JavaScript 的面向对象编程更加接近于 Java、C++ 等语言的风格。

尽管 ES6 类在语法上与其他面向对象语言相似,但在底层实现上,它仍然是基于 JavaScript 的原型继承机制。本文将详细介绍 ES6 类的语法、特性和使用方法。

ES6 类的基本语法

类的定义

使用 class 关键字定义一个类:

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

sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}

创建实例

使用 new 关键字创建类的实例:

javascript
const alice = new Person('Alice', 25);
alice.sayHello(); // 输出: Hello, my name is Alice and I am 25 years old.

类的构成部分

构造函数

constructor 是类的特殊方法,用于创建和初始化类的实例。一个类只能有一个 constructor 方法。

javascript
class Car {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}
}

const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.brand); // 输出: Toyota

实例方法

在类中定义的方法会被添加到类的原型上,所有实例都可以共享这些方法。

javascript
class Calculator {
add(a, b) {
return a + b;
}

subtract(a, b) {
return a - b;
}
}

const calc = new Calculator();
console.log(calc.add(5, 3)); // 输出: 8
console.log(calc.subtract(5, 3)); // 输出: 2

静态方法

使用 static 关键字定义类的静态方法,这些方法不需要实例化就可以调用,直接通过类名访问。

javascript
class MathUtils {
static square(x) {
return x * x;
}

static cube(x) {
return x * x * x;
}
}

console.log(MathUtils.square(3)); // 输出: 9
console.log(MathUtils.cube(3)); // 输出: 27

Getter 和 Setter

类可以包含 getter 和 setter 方法,用于获取和设置类的属性值。

javascript
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}

get celsius() {
return this._celsius;
}

set celsius(value) {
if (value < -273.15) {
throw new Error('Temperature below absolute zero is not possible');
}
this._celsius = value;
}

get fahrenheit() {
return this._celsius * 9/5 + 32;
}

set fahrenheit(value) {
this._celsius = (value - 32) * 5/9;
}
}

const temp = new Temperature(25);
console.log(temp.celsius); // 输出: 25
console.log(temp.fahrenheit); // 输出: 77

temp.celsius = 30;
console.log(temp.celsius); // 输出: 30
console.log(temp.fahrenheit); // 输出: 86

temp.fahrenheit = 68;
console.log(temp.celsius); // 输出: 20

类的继承

ES6 类支持通过 extends 关键字实现继承,这使得代码复用和层次结构的构建变得更加简单。

基本继承

javascript
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} makes a noise.`);
}
}

class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}

const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.

使用 super 关键字

super 关键字用于调用父类的构造函数或方法。

javascript
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} makes a noise.`);
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}

speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks.`);
}

getInfo() {
return `${this.name} is a ${this.breed}.`;
}
}

const dog = new Dog('Rex', 'German Shepherd');
dog.speak();
// 输出:
// Rex makes a noise.
// Rex barks.

console.log(dog.getInfo()); // 输出: Rex is a German Shepherd.

类的私有字段和方法

备注

私有字段和方法是 ECMAScript 的较新特性,可能需要现代浏览器或 Babel 等工具进行转译才能使用。

私有字段

使用 # 前缀定义私有字段,这些字段只能在类的内部访问。

javascript
class BankAccount {
#balance = 0;

constructor(initialBalance) {
if (initialBalance > 0) {
this.#balance = initialBalance;
}
}

deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}

withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}

getBalance() {
return this.#balance;
}
}

const account = new BankAccount(100);
console.log(account.getBalance()); // 输出: 100

account.deposit(50);
console.log(account.getBalance()); // 输出: 150

account.withdraw(30);
console.log(account.getBalance()); // 输出: 120

// 无法直接访问私有字段
// console.log(account.#balance); // 语法错误

实际应用案例

用户管理系统

下面是一个简单的用户管理系统示例,展示了如何使用类来组织代码:

javascript
class User {
#password;

constructor(username, email, password) {
this.username = username;
this.email = email;
this.#password = password;
this.createdAt = new Date();
this.isActive = true;
}

getInfo() {
return {
username: this.username,
email: this.email,
createdAt: this.createdAt,
isActive: this.isActive
};
}

verifyPassword(password) {
return this.#password === password;
}

changePassword(oldPassword, newPassword) {
if (this.verifyPassword(oldPassword)) {
this.#password = newPassword;
return true;
}
return false;
}
}

class AdminUser extends User {
constructor(username, email, password) {
super(username, email, password);
this.role = 'admin';
}

getInfo() {
const info = super.getInfo();
return { ...info, role: this.role };
}

deactivateUser(user) {
user.isActive = false;
console.log(`User ${user.username} has been deactivated.`);
}
}

// 使用示例
const regularUser = new User('john_doe', 'john@example.com', 'password123');
console.log(regularUser.getInfo());
// 输出: { username: 'john_doe', email: 'john@example.com', createdAt: [Date object], isActive: true }

const adminUser = new AdminUser('admin', 'admin@example.com', 'admin123');
console.log(adminUser.getInfo());
// 输出: { username: 'admin', email: 'admin@example.com', createdAt: [Date object], isActive: true, role: 'admin' }

// 管理员停用普通用户
adminUser.deactivateUser(regularUser);
console.log(regularUser.getInfo().isActive); // 输出: false

购物车系统

下面是一个购物车系统的简化示例:

javascript
class Product {
constructor(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}
}

class CartItem {
constructor(product, quantity = 1) {
this.product = product;
this.quantity = quantity;
}

get totalPrice() {
return this.product.price * this.quantity;
}

increaseQuantity(amount = 1) {
this.quantity += amount;
}

decreaseQuantity(amount = 1) {
if (this.quantity - amount >= 0) {
this.quantity -= amount;
return true;
}
return false;
}
}

class ShoppingCart {
#items = [];

addItem(product, quantity = 1) {
const existingItem = this.#items.find(item => item.product.id === product.id);

if (existingItem) {
existingItem.increaseQuantity(quantity);
} else {
this.#items.push(new CartItem(product, quantity));
}
}

removeItem(productId) {
const index = this.#items.findIndex(item => item.product.id === productId);
if (index !== -1) {
this.#items.splice(index, 1);
return true;
}
return false;
}

updateQuantity(productId, quantity) {
const item = this.#items.find(item => item.product.id === productId);
if (item) {
if (quantity <= 0) {
return this.removeItem(productId);
}
item.quantity = quantity;
return true;
}
return false;
}

getItems() {
return [...this.#items];
}

get totalPrice() {
return this.#items.reduce((total, item) => total + item.totalPrice, 0);
}

clear() {
this.#items = [];
}
}

// 使用示例
const laptop = new Product(1, 'Laptop', 999.99);
const phone = new Product(2, 'Smartphone', 499.99);
const headphones = new Product(3, 'Wireless Headphones', 149.99);

const cart = new ShoppingCart();

cart.addItem(laptop);
cart.addItem(phone, 2);
cart.addItem(headphones);

console.log(cart.getItems());
// 输出: 包含3个CartItem对象的数组

console.log(cart.totalPrice);
// 输出: 约2149.96 (999.99 + 2 * 499.99 + 149.99)

cart.updateQuantity(1, 2); // 更新笔记本电脑数量为2
console.log(cart.totalPrice);
// 输出: 约3149.95 (2 * 999.99 + 2 * 499.99 + 149.99)

cart.removeItem(3); // 移除耳机
console.log(cart.getItems().length); // 输出: 2

总结

ES6 类为 JavaScript 提供了一种更简洁、更直观的面向对象编程方式,虽然底层仍然是基于原型继承,但它的语法更接近于其他面向对象语言,使得代码的组织和复用变得更加清晰和简单。

ES6 类的主要特点包括:

  • 使用 class 关键字定义类
  • 构造函数用于初始化实例
  • 支持实例方法和静态方法
  • 支持 getter 和 setter
  • 通过 extends 实现继承
  • 使用 super 访问父类
  • 支持私有字段和方法(较新的特性)

通过掌握 ES6 类的使用,你可以编写出更加结构化、可维护的 JavaScript 代码,特别是在构建大型应用程序时,类提供的封装和继承机制能够帮助你更好地组织代码。

练习任务

为了巩固对 ES6 类的理解,尝试完成以下练习:

  1. 创建一个 Rectangle 类,包含计算面积和周长的方法,然后创建一个继承自 RectangleSquare 类。
  2. 实现一个简单的图书馆系统,包含 Book 类和 Library 类,支持添加书籍、借阅和归还功能。
  3. 创建一个带有私有字段的 Counter 类,只能通过类方法递增、递减和重置计数器。

进一步学习资源