JavaScript ES6类
引言
在 JavaScript ES6(ECMAScript 2015)之前,JavaScript 是通过原型继承来实现面向对象编程的,这种方式对于熟悉传统面向对象语言的开发者来说可能不太直观。ES6 引入的类(Class)语法提供了一种更清晰、更简洁的方式来创建对象和处理继承,使 JavaScript 的面向对象编程更加接近于 Java、C++ 等语言的风格。
尽管 ES6 类在语法上与其他面向对象语言相似,但在底层实现上,它仍然是基于 JavaScript 的原型继承机制。本文将详细介绍 ES6 类的语法、特性和使用方法。
ES6 类的基本语法
类的定义
使用 class
关键字定义一个类:
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
关键字创建类的实例:
const alice = new Person('Alice', 25);
alice.sayHello(); // 输出: Hello, my name is Alice and I am 25 years old.
类的构成部分
构造函数
constructor
是类的特殊方法,用于创建和初始化类的实例。一个类只能有一个 constructor
方法。
class Car {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}
}
const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.brand); // 输出: Toyota
实例方法
在类中定义的方法会被添加到类的原型上,所有实例都可以共享这些方法。
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
关键字定义类的静态方法,这些方法不需要实例化就可以调用,直接通过类名访问。
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 方法,用于获取和设置类的属性值。
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
关键字实现继承,这使得代码复用和层次结构的构建变得更加简单。
基本继承
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
关键字用于调用父类的构造函数或方法。
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 等工具进行转译才能使用。
私有字段
使用 #
前缀定义私有字段,这些字段只能在类的内部访问。
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); // 语法错误
实际应用案例
用户管理系统
下面是一个简单的用户管理系统示例,展示了如何使用类来组织代码:
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
购物车系统
下面是一个购物车系统的简化示例:
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 类的理解,尝试完成以下练习:
- 创建一个
Rectangle
类,包含计算面积和周长的方法,然后创建一个继承自Rectangle
的Square
类。 - 实现一个简单的图书馆系统,包含
Book
类和Library
类,支持添加书籍、借阅和归还功能。 - 创建一个带有私有字段的
Counter
类,只能通过类方法递增、递减和重置计数器。