JavaScript Class
1. Class 语法糖
在 ES6 之前,我们使用构造函数和原型链来实现对象的创建和继承。ES6 引入了 class
关键字,它提供了一种更清晰、更简洁的语法。
Class 的底层实现仍然是基于原型继承。因此,
class
被称为“语法糖”。
构造函数 vs. Class 写法对比:
// 传统构造函数写法
function Player(name, marker) {
this.name = name;
this.marker = marker;
}
Player.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
// ES6 Class 写法
class Player {
constructor(name, marker) {
this.name = name;
this.marker = marker;
}
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
}
const player1 = new Player('Alice', 'X');
player1.sayHello(); // "Hello, I'm Alice"
尽管 class
看起来更像传统的面向对象语言,但它和构造函数有几个关键区别:
- 不存在变量提升:与函数不同,
class
声明不会被提升。必须先声明class
,然后才能使用它。 - 方法不可枚举:在
class
内部定义的方法(如sayHello
)默认是不可枚举的,而原型上添加的方法(Player.prototype.sayHello
)默认是可枚举的。
2. 基本语法与构造函数
class
的核心是 constructor
方法。这是一个特殊的函数,用于接收参数并初始化实例的属性(即 this.xxx = ...
)。当我们使用 new
关键字创建类的一个新实例时,它会被自动调用。
- 一个
class
只能有一个constructor
方法。
class Book {
// 当 new Book(...) 时,constructor 会被调用
constructor(title, author, pages) {
this.title = title;
this.author = author;
this.pages = pages;
}
// 在 class 中定义的函数,会自动成为实例的原型方法
read() {
console.log(`You are reading "${this.title}" by ${this.author}.`);
}
}
const book1 = new Book('The Hobbit', 'J.R.R. Tolkien', 295);
book1.read(); // "You are reading "The Hobbit" by J.R.R. Tolkien."
3. Getters 和 Setters
Getters 和 Setters 允许我们定义在获取或设置属性时执行的函数。它们让我们能像访问普通属性一样调用这些函数,从而实现对属性访问的控制。
get
:用于获取属性值,可以进行计算或返回一个内部变量。set
:用于设置属性值,通常用于在更新属性前进行验证或处理。
它们常用于操作一个由
_
开头的“伪私有”属性。
class User {
constructor(email) {
this.email = email;
}
// Getter: 像访问属性一样调用 user.username
get username() {
return this.email.split('@')[0];
}
// Setter: 像给属性赋值一样调用 user.email = "..."
set email(newEmail) {
if (newEmail.includes('@')) {
this._email = newEmail;
} else {
console.error('Invalid email format.');
}
}
// Getter for the internal property
get email() {
return this._email;
}
}
const user = new User('john.doe@example.com');
console.log(user.username); // 'john.doe' (Getter 被调用)
user.email = 'jane.doe@example.com'; // Setter 被调用
console.log(user.email); // 'jane.doe@example.com'
user.email = 'invalid-email'; // Setter 被调用,并打印错误
4. 继承
class
通过 extends
关键字来实现继承,这比旧的原型链继承方式直观得多。
extends
:让一个类(子类)继承另一个类(父类)的属性和方法。super
:一个特殊的关键字,用于调用父类的构造函数和方法。- 在子类的
constructor
中,必须在使用this
之前调用super()
,以完成父类的初始化。 super.methodName()
可以调用父类原型上的方法。
- 在子类的
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`Hello, I'm ${this.name}!`);
}
}
// Admin 类继承自 Person 类
class Admin extends Person {
constructor(name, role) {
// 1. 调用父类的 constructor(name)
super(name);
// 2. 添加子类自己的属性
this.role = role;
}
// 覆盖父类的方法
sayName() {
// 3. 通过 super 调用父类的同名方法
super.sayName();
console.log(`My role is ${this.role}.`);
}
}
const admin = new Admin('Gandalf', 'Head of Security');
admin.sayName();
// "Hello, I'm Gandalf!" (来自 super.sayName())
// "My role is Head of Security." (来自子类自己的逻辑)
使用原型链的继承方法在前面的文章中有详细的讲解。
5. 私有属性和方法
过去,JavaScript 开发者通过在属性名前加下划线(如 _privateVar
)来“约定”一个属性是私有的,但这并没有真正的限制。现在,我们可以使用 #
前缀来创建真正的私有类成员。
- 私有字段/方法:以
#
开头的成员只能在class
内部被访问。 - 强制封装:从外部访问私有成员会直接抛出语法错误。
class BankAccount {
// #balance 是一个私有字段
#balance = 0;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#logTransaction('deposit', amount);
}
}
// #logTransaction 是一个私有方法
#logTransaction(type, amount) {
console.log(`Logged: ${type} of ${amount}. New balance: ${this.#balance}`);
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
account.deposit(50); // "Logged: deposit of 50. New balance: 150"
console.log(account.getBalance()); // 150
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
// account.#logTransaction(); // SyntaxError
6. 静态属性和方法
静态成员(Static Members)是直接附加在 class
自身上,而不是 class
实例上的属性或方法。它们通常用于创建工具函数或类级别的常量,它通过类名直接调用,而不是通过实例调用。
我们使用 static
关键字来定义静态属性或方法:
class MathHelper {
// 静态属性
static PI = 3.14159;
// 静态方法
static calculateCircumference(radius) {
return 2 * this.PI * radius; // 在静态方法中,this 指向类本身
}
}
// 直接通过类名访问静态成员
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.calculateCircumference(10)); // 62.8318
const instance = new MathHelper();
// console.log(instance.PI); // undefined (静态成员在实例上不存在)