Appearance
JavaScript 类
什么是类
类(Class)是 ES6(ECMAScript 2015)引入的一种新语法,用于创建对象和实现面向对象编程。类是一种模板,用于定义对象的属性和方法。
在 ES6 之前,JavaScript 使用构造函数和原型链来实现面向对象编程。ES6 类语法提供了一种更简洁、更清晰的方式来定义类,使得 JavaScript 的面向对象编程更加接近传统的面向对象语言(如 Java、C++)。
类的基本语法
1. 类的定义
javascript
// 基本类定义
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hello, my name is ${this.name}`;
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
}
// 使用类
const person = new Person("John", 30);
console.log(person.name); // 'John'
console.log(person.age); // 30
console.log(person.greet()); // 'Hello, my name is John'
// 使用静态方法
const person2 = Person.create("Jane", 25);
console.log(person2.name); // 'Jane'
console.log(person2.age); // 252. 类的表达式
类也可以使用表达式的形式定义,类似于函数表达式。
javascript
// 类表达式
const Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
};
// 命名类表达式
const Person = class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
};
// 使用类
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John'3. 类的继承
类可以通过 extends 关键字继承其他类。
javascript
// 父类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
// 子类
class Student extends Person {
constructor(name, age, grade) {
// 调用父类的构造函数
super(name, age);
this.grade = grade;
}
// 重写父类的方法
greet() {
return `Hello, my name is ${this.name}, I'm in grade ${this.grade}`;
}
// 子类的方法
study() {
return `${this.name} is studying`;
}
}
// 使用子类
const student = new Student("John", 15, 9);
console.log(student.name); // 'John'
console.log(student.age); // 15
console.log(student.grade); // 9
console.log(student.greet()); // 'Hello, my name is John, I'm in grade 9'
console.log(student.study()); // 'John is studying'
// 检查实例
console.log(student instanceof Student); // true
console.log(student instanceof Person); // true4. 静态方法和属性
静态方法和属性属于类本身,而不是类的实例。
javascript
class Person {
// 静态属性
static species = "Homo sapiens";
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hello, my name is ${this.name}`;
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
// 静态方法
static getSpecies() {
return this.species;
}
}
// 访问静态属性
console.log(Person.species); // 'Homo sapiens'
// 调用静态方法
const person = Person.create("John", 30);
console.log(person.name); // 'John'
console.log(Person.getSpecies()); // 'Homo sapiens'
// 错误:静态属性和方法不能通过实例访问
// console.log(person.species); // undefined
// console.log(person.create); // undefined5. Getter 和 Setter
Getter 和 Setter 允许我们定义对象属性的读取和设置行为。
javascript
class Person {
constructor(name, age) {
this.name = name;
this._age = age;
}
// Getter
get age() {
return this._age;
}
// Setter
set age(value) {
if (value < 0) {
throw new Error("年龄不能为负数");
}
this._age = value;
}
// Getter
get fullName() {
return this.name;
}
}
// 使用 Getter 和 Setter
const person = new Person("John", 30);
console.log(person.age); // 30(调用 getter)
person.age = 31; // 调用 setter
console.log(person.age); // 31
// 错误:年龄不能为负数
// person.age = -1; // Error: 年龄不能为负数
console.log(person.fullName); // 'John'(调用 getter)6. 私有属性和方法
私有属性和方法是 ES2022 引入的特性,使用 # 前缀表示,只能在类内部访问。
javascript
class Person {
// 私有属性
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
// 实例方法访问私有属性
greet() {
return `Hello, my name is ${this.#name}, I'm ${this.#age} years old`;
}
// 私有方法
#validateAge(age) {
return age >= 0;
}
// 实例方法调用私有方法
setAge(age) {
if (this.#validateAge(age)) {
this.#age = age;
} else {
throw new Error("年龄不能为负数");
}
}
getAge() {
return this.#age;
}
}
// 使用类
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John, I'm 30 years old'
person.setAge(31);
console.log(person.getAge()); // 31
// 错误:私有属性不能从外部访问
// console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class
// 错误:私有方法不能从外部访问
// person.#validateAge(32); // SyntaxError: Private field '#validateAge' must be declared in an enclosing class7. 类的字段初始化
类的字段初始化允许我们在类定义中直接初始化实例属性,而不需要在构造函数中赋值。
javascript
class Person {
// 实例属性初始化
name = "Unknown";
age = 0;
// 静态属性初始化
static species = "Homo sapiens";
constructor(name, age) {
if (name) {
this.name = name;
}
if (age) {
this.age = age;
}
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
// 使用类
const person1 = new Person("John", 30);
console.log(person1.name); // 'John'
console.log(person1.age); // 30
const person2 = new Person();
console.log(person2.name); // 'Unknown'
console.log(person2.age); // 0
console.log(Person.species); // 'Homo sapiens'类的特性
1. 严格模式
类内部默认在严格模式下运行,不需要显式添加 'use strict;'。
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
// 严格模式下,this 为 undefined(如果不是通过实例调用)
return `Hello, my name is ${this.name}`;
}
}
const person = new Person("John");
console.log(person.greet()); // 'Hello, my name is John'
// 错误:在严格模式下,this 为 undefined
const greet = person.greet;
// console.log(greet()); // TypeError: Cannot read property 'name' of undefined2. 构造函数
构造函数是类的特殊方法,当使用 new 关键字创建实例时会调用。
- 每个类只能有一个构造函数
- 如果没有定义构造函数,会默认生成一个空的构造函数
- 子类必须在构造函数中调用
super()来调用父类的构造函数
javascript
// 没有构造函数的类
class Person {
name = "Unknown";
greet() {
return `Hello, my name is ${this.name}`;
}
}
const person = new Person();
console.log(person.name); // 'Unknown'
// 子类必须调用 super()
class Student extends Person {
constructor(name, grade) {
// 必须先调用 super()
super(name);
this.grade = grade;
}
}
const student = new Student("John", 9);
console.log(student.name); // 'John'
console.log(student.grade); // 93. 方法定义
类中的方法定义是简写形式,不需要使用 function 关键字。
javascript
class Person {
constructor(name) {
this.name = name;
}
// 方法定义(简写形式)
greet() {
return `Hello, my name is ${this.name}`;
}
// 错误:不能使用 function 关键字
// function greet() {
// return `Hello, my name is ${this.name}`;
// }
}4. 原型链
类在底层仍然使用原型链实现,类的方法会被添加到类的原型对象上。
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
const person = new Person("John");
// 方法存在于原型对象上
console.log(person.greet === Person.prototype.greet); // true
// 可以通过原型对象添加方法
Person.prototype.sayGoodbye = function () {
return `Goodbye, ${this.name}`;
};
console.log(person.sayGoodbye()); // 'Goodbye, John'5. 类的提升
类声明不会被提升,必须在定义后使用。
javascript
// 错误:类声明不会被提升
// const person = new Person('John'); // ReferenceError: Person is not defined
class Person {
constructor(name) {
this.name = name;
}
}
// 正确:在定义后使用
const person = new Person("John");
console.log(person.name); // 'John'类的应用场景
1. 面向对象编程
类是实现面向对象编程的重要工具,它允许我们创建具有相似属性和方法的对象。
javascript
// 面向对象编程示例
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows`;
}
}
const dog = new Dog("Rex");
console.log(dog.speak()); // 'Rex barks'
const cat = new Cat("Whiskers");
console.log(cat.speak()); // 'Whiskers meows'2. 组件开发
在前端框架(如 React、Vue)中,类可以用于创建组件。
javascript
// React 组件示例
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.increment()}>Increment</button>
</div>
);
}
}
// Vue 组件示例
class CounterComponent {
constructor(el) {
this.el = el;
this.count = 0;
this.render();
this.bindEvents();
}
render() {
this.el.innerHTML = `
<p>Count: ${this.count}</p>
<button>Increment</button>
`;
}
bindEvents() {
this.el.querySelector("button").addEventListener("click", () => {
this.count++;
this.render();
this.bindEvents();
});
}
}
// 使用组件
new CounterComponent(document.getElementById("counter"));3. 工具类
类可以用于创建工具类,提供一组相关的静态方法。
javascript
// 工具类示例
class MathUtils {
// 静态方法
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
static multiply(a, b) {
return a * b;
}
static divide(a, b) {
if (b === 0) {
throw new Error("除数不能为 0");
}
return a / b;
}
// 静态方法
static factorial(n) {
if (n < 0) {
throw new Error("参数不能为负数");
}
if (n === 0 || n === 1) {
return 1;
}
return n * this.factorial(n - 1);
}
}
// 使用工具类
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.subtract(5, 2)); // 3
console.log(MathUtils.multiply(2, 3)); // 6
console.log(MathUtils.divide(6, 2)); // 3
console.log(MathUtils.factorial(5)); // 1204. 数据模型
类可以用于创建数据模型,表示应用中的实体。
javascript
// 数据模型示例
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
// 实例方法
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt,
};
}
// 静态方法
static fromJSON(json) {
return new User(json.id, json.name, json.email);
}
// 静态方法
static validate(user) {
return !!(user.name && user.email);
}
}
// 使用数据模型
const user = new User(1, "John", "john@example.com");
console.log(user.toJSON()); // { id: 1, name: 'John', email: 'john@example.com', createdAt: Date }
const userJson = { id: 2, name: "Jane", email: "jane@example.com" };
const user2 = User.fromJSON(userJson);
console.log(user2.name); // 'Jane'
console.log(User.validate(user)); // true
console.log(User.validate({ name: "John" })); // false5. 单例模式
类可以用于实现单例模式,确保一个类只有一个实例。
javascript
// 单例模式示例
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.init();
Singleton.instance = this;
}
init() {
this.value = 0;
}
increment() {
this.value++;
return this.value;
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 使用单例
const instance1 = new Singleton();
console.log(instance1.increment()); // 1
const instance2 = new Singleton();
console.log(instance2.increment()); // 2
console.log(instance1 === instance2); // true
const instance3 = Singleton.getInstance();
console.log(instance3.increment()); // 3
console.log(instance1 === instance3); // true类的最佳实践
1. 命名规范
- 类名:使用 PascalCase(首字母大写)
- 方法名:使用 camelCase(首字母小写)
- 属性名:使用 camelCase(首字母小写)
- 私有属性和方法:使用
#前缀
javascript
// 好的命名
class Person {
#privateField;
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.#privateField = "private";
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
#privateMethod() {
// 私有方法
}
}
// 不好的命名
class person {
_privateField;
constructor(first_name, last_name) {
this.first_name = first_name;
this.last_name = last_name;
}
get_full_name() {
return `${this.first_name} ${this.last_name}`;
}
}2. 单一职责
每个类应该只负责一个特定的功能,保持类的简洁和专注。
javascript
// 好的做法:单一职责
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
}
toJSON() {
return { id: this.id, name: this.name, email: this.email };
}
}
class UserService {
static async getUser(id) {
const response = await fetch(`/api/users/${id}`);
return User.fromJSON(await response.json());
}
static async saveUser(user) {
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user.toJSON()),
});
return User.fromJSON(await response.json());
}
}
// 不好的做法:多个职责
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
}
toJSON() {
return { id: this.id, name: this.name, email: this.email };
}
// 不应该在 User 类中处理 API 请求
async save() {
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(this.toJSON()),
});
return User.fromJSON(await response.json());
}
}3. 继承的使用
- 只在真正需要继承关系时使用继承
- 遵循 Liskov 替换原则(子类应该能够替换父类)
- 避免深层继承,优先使用组合
javascript
// 好的做法:合理使用继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks`;
}
}
// 不好的做法:深层继承
class Animal {
// ...
}
class Mammal extends Animal {
// ...
}
class Carnivore extends Mammal {
// ...
}
class Dog extends Carnivore {
// ...
}4. 构造函数
- 构造函数应该简洁,只负责初始化实例属性
- 避免在构造函数中执行复杂的逻辑
- 对于复杂的初始化,可以使用静态工厂方法
javascript
// 好的做法:简洁的构造函数
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// 静态工厂方法
static createAdult(name) {
return new Person(name, 18);
}
static createChild(name) {
return new Person(name, 10);
}
}
// 不好的做法:复杂的构造函数
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
// 不应该在构造函数中执行复杂的逻辑
this.validate();
this.loadData();
this.initEvents();
}
validate() {
// 验证逻辑
}
loadData() {
// 加载数据
}
initEvents() {
// 初始化事件
}
}5. 错误处理
- 在类中适当处理错误,使用 try-catch 捕获异常
- 对于无效的参数,抛出明确的错误信息
- 对于异步操作,返回 Promise 并正确处理错误
javascript
// 好的做法:错误处理
class Calculator {
divide(a, b) {
if (b === 0) {
throw new Error("除数不能为 0");
}
return a / b;
}
async fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Error fetching data:", error);
throw error;
}
}
}
// 使用
const calculator = new Calculator();
try {
console.log(calculator.divide(6, 2)); // 3
console.log(calculator.divide(6, 0)); // 抛出错误
} catch (error) {
console.error("Error:", error.message);
}类的兼容性
1. 浏览器兼容性
| 特性 | Chrome | Firefox | Safari | Edge | IE |
|---|---|---|---|---|---|
| 基本类语法 | 49+ | 45+ | 9.1+ | 13+ | 不支持 |
| 类的继承 | 49+ | 45+ | 9.1+ | 13+ | 不支持 |
| 静态方法 | 49+ | 45+ | 9.1+ | 13+ | 不支持 |
| Getter 和 Setter | 49+ | 45+ | 9.1+ | 13+ | 不支持 |
| 类的字段初始化 | 72+ | 69+ | 14.1+ | 79+ | 不支持 |
| 私有属性和方法 | 80+ | 74+ | 14.1+ | 80+ | 不支持 |
2. 转译工具
为了在旧版本浏览器中使用类,可以使用转译工具如 Babel 进行转译。
javascript
// Babel 配置示例 (.babelrc)
{
"presets": ["@babel/preset-env"]
}3. 替代方案
在不支持类的环境中,可以使用构造函数和原型链来实现类似的功能。
javascript
// 构造函数和原型链
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
// 静态方法
Person.create = function (name, age) {
return new Person(name, age);
};
// 继承
function Student(name, age, grade) {
Person.call(this, name, age);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.greet = function () {
return `Hello, my name is ${this.name}, I'm in grade ${this.grade}`;
};
// 使用
const person = new Person("John", 30);
console.log(person.greet()); // 'Hello, my name is John'
const student = new Student("Jane", 15, 9);
console.log(student.greet()); // 'Hello, my name is Jane, I'm in grade 9'总结
类是 ES6 引入的一种新语法,用于创建对象和实现面向对象编程。它具有以下特点:
- 简洁的语法:提供了更清晰、更简洁的语法来定义类和方法
- 继承:支持通过
extends关键字继承其他类 - 静态方法和属性:支持定义属于类本身的方法和属性
- Getter 和 Setter:允许定义对象属性的读取和设置行为
- 私有属性和方法:支持定义只能在类内部访问的属性和方法
- 严格模式:默认在严格模式下运行
- 原型链:底层仍然使用原型链实现
类的应用场景非常广泛,包括:
- 面向对象编程
- 组件开发
- 工具类
- 数据模型
- 设计模式(如单例模式)
通过合理使用类,我们可以编写更加结构化、可维护和可扩展的 JavaScript 代码。