跳到主要内容

JavaScript 类语法

什么是JavaScript类?

JavaScript类是ES6(ECMAScript 2015)引入的一个语法糖,它提供了一种更清晰、更面向对象的方式来创建对象和处理继承。在ES6之前,JavaScript使用原型和构造函数来实现类似的功能,但语法不够直观。类语法让JavaScript的面向对象编程变得更加简单和符合直觉。

备注

虽然JavaScript类在语法上类似于Java或C++等传统面向对象语言,但底层实现仍然基于JavaScript的原型继承机制。

类的基本语法

类的声明

在JavaScript中,可以使用class关键字声明一个类:

javascript
class Person {
// 类的内容
}

构造函数

构造函数是一个特殊的方法,用于创建和初始化类的实例。每个类只能有一个名为constructor的方法:

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

// 创建Person类的实例
const person1 = new Person('Alice', 25);
console.log(person1.name); // 输出: Alice
console.log(person1.age); // 输出: 25

类的方法

在类中定义方法非常简单,不需要使用function关键字:

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

// 实例方法
greet() {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}

// 另一个实例方法
haveBirthday() {
this.age++;
return `${this.name} is now ${this.age} years old.`;
}
}

const person1 = new Person('Bob', 30);
console.log(person1.greet()); // 输出: Hello, my name is Bob and I am 30 years old.
console.log(person1.haveBirthday()); // 输出: Bob is now 31 years old.

类的高级特性

Getter和Setter

JavaScript类支持getter和setter方法,可以通过它们控制对类属性的访问:

javascript
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}

// Getter
get fullName() {
return `${this._firstName} ${this._lastName}`;
}

// Setter
set fullName(name) {
const parts = name.split(' ');
this._firstName = parts[0];
this._lastName = parts[1];
}
}

const person = new Person('John', 'Doe');
console.log(person.fullName); // 输出: John Doe

person.fullName = 'Jane Smith';
console.log(person.fullName); // 输出: Jane Smith
console.log(person._firstName); // 输出: Jane
提示

使用下划线前缀(如_firstName)是一种常见的约定,表示该属性应该被视为私有的,虽然在技术上它仍然可以被外部访问。

静态方法和属性

静态方法和属性属于类本身,而不是类的实例。使用static关键字声明静态成员:

javascript
class MathUtils {
static PI = 3.14159;

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

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

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

// 注意:不能通过实例访问静态方法
const mathInstance = new MathUtils();
// console.log(mathInstance.square(3)); // 这会导致错误

类的继承

JavaScript类可以通过extends关键字实现继承,子类可以继承父类的属性和方法:

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

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

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

// 重写父类方法
speak() {
return `${this.name} barks!`;
}

// 新增的子类方法
getBreed() {
return `${this.name} is a ${this.breed}.`;
}
}

const animal = new Animal('Generic Animal');
console.log(animal.speak()); // 输出: Generic Animal makes a noise.

const dog = new Dog('Rex', 'German Shepherd');
console.log(dog.speak()); // 输出: Rex barks!
console.log(dog.getBreed()); // 输出: Rex is a German Shepherd.
警告

在子类构造函数中,必须在使用this之前调用super(),否则会抛出引用错误。

私有字段和方法

从ES2022(ES13)开始,JavaScript支持真正的私有字段和方法,使用#前缀声明:

javascript
class BankAccount {
// 私有字段
#balance = 0;
#accountNumber;

constructor(accountHolder, initialBalance) {
this.accountHolder = accountHolder;
this.#accountNumber = Math.floor(Math.random() * 1000000);
this.#balance = initialBalance;
}

// 公共方法
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction('deposit', amount);
return `Deposited $${amount}. New balance: $${this.#balance}`;
}
return 'Invalid deposit amount';
}

withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
this.#logTransaction('withdrawal', amount);
return `Withdrew $${amount}. New balance: $${this.#balance}`;
}
return 'Invalid withdrawal amount or insufficient funds';
}

getBalance() {
return `Current balance: $${this.#balance}`;
}

// 私有方法
#logTransaction(type, amount) {
console.log(`[Account ${this.#accountNumber}] ${type}: $${amount} at ${new Date()}`);
}
}

const account = new BankAccount('John Doe', 1000);
console.log(account.getBalance()); // 输出: Current balance: $1000
console.log(account.deposit(500)); // 输出: Deposited $500. New balance: $1500
console.log(account.withdraw(200)); // 输出: Withdrew $200. New balance: $1300

// 尝试直接访问私有字段会导致错误
// console.log(account.#balance); // SyntaxError
// account.#logTransaction('test', 100); // SyntaxError

实际应用案例

案例1:用户认证系统

javascript
class User {
#password;

constructor(username, password) {
this.username = username;
this.#password = this.#hashPassword(password);
this.lastLogin = new Date();
this.isLoggedIn = false;
}

#hashPassword(password) {
// 实际应用中应使用安全的哈希算法
return password.split('').reverse().join('') + 'hashed';
}

login(password) {
if (this.#verifyPassword(password)) {
this.lastLogin = new Date();
this.isLoggedIn = true;
return true;
}
return false;
}

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

logout() {
this.isLoggedIn = false;
return `${this.username} logged out successfully`;
}

changePassword(oldPassword, newPassword) {
if (this.#verifyPassword(oldPassword)) {
this.#password = this.#hashPassword(newPassword);
return 'Password changed successfully';
}
return 'Incorrect password';
}
}

class AdminUser extends User {
constructor(username, password) {
super(username, password);
this.role = 'admin';
this.permissions = ['read', 'write', 'delete', 'manage-users'];
}

grantPermission(user, permission) {
if (this.isLoggedIn && user instanceof User) {
return `Permission ${permission} granted to ${user.username}`;
}
return 'Operation not permitted';
}
}

// 使用示例
const regularUser = new User('john_doe', 'secret123');
console.log(regularUser.login('secret123')); // 输出: true
console.log(regularUser.isLoggedIn); // 输出: true
console.log(regularUser.changePassword('secret123', 'newSecret')); // 输出: Password changed successfully

const admin = new AdminUser('admin', 'admin123');
console.log(admin.login('admin123')); // 输出: true
console.log(admin.grantPermission(regularUser, 'edit')); // 输出: Permission edit granted to john_doe

案例2:电子商务购物车

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

formatPrice() {
return `$${this.price.toFixed(2)}`;
}
}

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

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

updateQuantity(newQuantity) {
if (newQuantity >= 0) {
this.quantity = newQuantity;
}
}
}

class ShoppingCart {
#items = [];

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

if (existingItem) {
existingItem.updateQuantity(existingItem.quantity + 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.updateQuantity(quantity);
return true;
}
return false;
}

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

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

get itemCount() {
return this.#items.reduce((count, item) => count + item.quantity, 0);
}

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

// 使用示例
const laptop = new Product(1, "MacBook Pro", 1299.99, "13-inch, 8GB RAM, 256GB SSD");
const phone = new Product(2, "iPhone 13", 799.99, "128GB, Blue");
const headphones = new Product(3, "AirPods Pro", 249.99, "Active Noise Cancellation");

const cart = new ShoppingCart();

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

console.log(`Items in cart: ${cart.itemCount}`); // 输出: Items in cart: 4
console.log(`Total: $${cart.total.toFixed(2)}`); // 输出: Total: $3149.96

cart.updateQuantity(2, 1); // 更新iPhone的数量为1
console.log(`Items in cart after update: ${cart.itemCount}`); // 输出: Items in cart after update: 3
console.log(`Total after update: $${cart.total.toFixed(2)}`); // 输出: Total after update: $2349.97

总结

JavaScript的类语法提供了一种简洁、直观的方式来创建和扩展对象,实现面向对象编程。主要特性包括:

  1. 类声明:使用class关键字创建类
  2. 构造函数:使用constructor方法初始化实例
  3. 实例方法:为类的实例定义方法
  4. Getter和Setter:控制属性的访问和修改
  5. 静态方法和属性:属于类本身而非实例的成员
  6. 继承:通过extends关键字实现类的继承
  7. 私有字段和方法:使用#前缀创建真正的封装

尽管类语法是ES6引入的语法糖,底层仍然基于JavaScript的原型继承,但它大大简化了面向对象代码的编写,提高了代码的可读性和可维护性。

练习

  1. 创建一个Rectangle类,包含长度和宽度属性,以及计算面积和周长的方法。
  2. 扩展Rectangle类,创建一个Square类(正方形),确保长度和宽度始终相等。
  3. 创建一个Vehicle基类,然后创建CarMotorcycle子类,添加适当的属性和方法。
  4. 实现一个简单的TodoList类,包含添加、删除、标记完成等功能。

进一步学习资源