Appearance
JS 类
类的概念
类是 JavaScript 中用于创建对象的蓝图或模板。ES6 引入了 class 关键字,使类的定义更加简洁和清晰。类是基于原型继承的语法糖,提供了一种更面向对象的编程方式。
类的定义
1. 基本语法
使用 class 关键字定义类:
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}!`;
}
getAge() {
return this.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!
console.log(person.getAge()); // 输出: 302. 类表达式
类也可以使用表达式的形式定义:
javascript
// 命名类表达式
const Person = class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}!`;
}
};
// 匿名类表达式
const Person2 = class {
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!
const person2 = new Person2('Jane', 25);
console.log(person2.greet()); // 输出: Hello, my name is Jane!3. 类的特点
- 类声明不会被提升:类声明不会像函数声明那样被提升,因此必须在定义类之后才能使用它
- 类体中的代码在严格模式下执行:类体中的代码自动在严格模式下执行,无需显式添加
'use strict' - 类方法是不可枚举的:类定义的方法默认是不可枚举的
- 类构造函数必须使用
new调用:直接调用类构造函数会抛出错误
javascript
// 错误:类声明不会被提升
// const person = new Person('John', 30); // 错误:Person 未定义
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 正确:在定义类之后创建实例
const person = new Person('John', 30);
// 错误:类构造函数必须使用 new 调用
// const person2 = Person('John', 30); // 错误:Class constructor Person cannot be invoked without 'new'
// 类方法是不可枚举的
console.log(Object.keys(person)); // 输出: ["name", "age"](不包含方法)类的成员
1. 构造函数
构造函数是类中的特殊方法,用于初始化实例:
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = new Person('John', 30);
console.log(person.name); // 输出: John
console.log(person.age); // 输出: 30
// 构造函数是可选的
class EmptyClass {
// 没有构造函数,会使用默认构造函数
}
const empty = new EmptyClass();
console.log(empty); // 输出: EmptyClass {}2. 实例方法
实例方法是定义在类原型上的方法,每个实例都可以访问:
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}!`;
}
sayHello(to) {
return `Hello ${to}, I'm ${this.name}!`;
}
}
const person = new Person('John');
console.log(person.greet()); // 输出: Hello, my name is John!
console.log(person.sayHello('Jane')); // 输出: Hello Jane, I'm John!3. 获取器和设置器
获取器(getter)和设置器(setter)允许我们控制属性的访问和修改:
javascript
class Person {
constructor(name) {
this._name = name; // 使用下划线表示私有属性
}
get name() {
return this._name;
}
set name(newName) {
if (typeof newName === 'string' && newName.length > 0) {
this._name = newName;
} else {
console.error('Name must be a non-empty string');
}
}
}
const person = new Person('John');
console.log(person.name); // 输出: John(调用 get name())
person.name = 'Jane'; // 调用 set name()
console.log(person.name); // 输出: Jane
person.name = ''; // 输出: Name must be a non-empty string
console.log(person.name); // 输出: Jane(名称未更改)4. 静态方法
静态方法是定义在类本身上的方法,而不是实例上的方法:
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static createAdult(name) {
return new Person(name, 18);
}
static compareAge(person1, person2) {
return person1.age - person2.age;
}
}
// 调用静态方法
const adult = Person.createAdult('John');
console.log(adult.name); // 输出: John
console.log(adult.age); // 输出: 18
const person1 = new Person('John', 30);
const person2 = new Person('Jane', 25);
console.log(Person.compareAge(person1, person2)); // 输出: 5
// 实例不能访问静态方法
// console.log(person1.createAdult); // 输出: undefined5. 静态属性
静态属性是定义在类本身上的属性:
javascript
class Person {
static species = 'Homo sapiens';
static planet = 'Earth';
constructor(name) {
this.name = name;
}
}
console.log(Person.species); // 输出: Homo sapiens
console.log(Person.planet); // 输出: Earth
// 实例不能访问静态属性
const person = new Person('John');
console.log(person.species); // 输出: undefined6. 实例属性
实例属性可以在构造函数中定义,也可以在类体中直接定义:
javascript
class Person {
// 直接在类体中定义实例属性(ES2022+)
isAlive = true;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = new Person('John', 30);
console.log(person.name); // 输出: John
console.log(person.age); // 输出: 30
console.log(person.isAlive); // 输出: true类的继承
1. 基本语法
使用 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;
}
study() {
return `${this.name} is studying in grade ${this.grade}!`;
}
}
const student = new Student('John', 15, 10);
console.log(student.name); // 输出: John
console.log(student.age); // 输出: 15
console.log(student.grade); // 输出: 10
console.log(student.greet()); // 输出: Hello, my name is John!(继承自父类)
console.log(student.study()); // 输出: John is studying in grade 10!(子类特有)2. 方法重写
子类可以重写父类的方法:
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}!`;
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
// 重写父类的 greet 方法
greet() {
return `Hello, I'm ${this.name}, a student in grade ${this.grade}!`;
}
}
const person = new Person('John');
console.log(person.greet()); // 输出: Hello, my name is John!
const student = new Student('John', 10);
console.log(student.greet()); // 输出: Hello, I'm John, a student in grade 10!(重写后的方法)3. 调用父类方法
子类可以使用 super 关键字调用父类的方法:
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}!`;
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
greet() {
const parentGreet = super.greet(); // 调用父类的 greet 方法
return `${parentGreet} I'm a student in grade ${this.grade}!`;
}
}
const student = new Student('John', 10);
console.log(student.greet()); // 输出: Hello, my name is John! I'm a student in grade 10!类的应用
1. 面向对象编程
类提供了一种面向对象的编程方式,使代码更加结构化和可维护:
javascript
class Shape {
constructor(color) {
this.color = color;
}
getColor() {
return this.color;
}
// 抽象方法,子类必须实现
calculateArea() {
throw new Error('Subclass must implement calculateArea()');
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
}
const circle = new Circle('red', 5);
console.log(circle.getColor()); // 输出: red
console.log(circle.calculateArea()); // 输出: 78.53981633974483
const rectangle = new Rectangle('blue', 4, 6);
console.log(rectangle.getColor()); // 输出: blue
console.log(rectangle.calculateArea()); // 输出: 242. 模块化
类可以用于模块化代码,将相关的属性和方法组织在一起:
javascript
// 数学模块
class Calculator {
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('Division by zero');
}
return a / b;
}
}
console.log(Calculator.add(5, 10)); // 输出: 15
console.log(Calculator.subtract(10, 5)); // 输出: 5
console.log(Calculator.multiply(5, 10)); // 输出: 50
console.log(Calculator.divide(10, 2)); // 输出: 53. 单例模式
使用类可以实现单例模式:
javascript
class Singleton {
static instance;
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.value = Math.random();
Singleton.instance = this;
}
getValue() {
return this.value;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // 输出: true
console.log(instance1.getValue()); // 输出: 随机值
console.log(instance2.getValue()); // 输出: 与上面相同的随机值类的最佳实践
1. 命名规范
类名应该使用帕斯卡命名法(PascalCase):
javascript
// 好的做法
class Person {
// ...
}
// 不好的做法
class person {
// ...
}2. 使用构造函数初始化
使用构造函数初始化实例属性:
javascript
// 好的做法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 不好的做法
class Person {
name;
age;
setInfo(name, age) {
this.name = name;
this.age = age;
}
}3. 合理使用继承
只在真正需要继承的情况下使用继承:
javascript
// 好的做法:有明确的继承关系
class Animal {
constructor(name) {
this.name = name;
}
speak() {
throw new Error('Subclass must implement speak()');
}
}
class Dog extends Animal {
speak() {
return 'Woof!';
}
}
// 不好的做法:没有明确的继承关系
class Person {
constructor(name) {
this.name = name;
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
}4. 使用获取器和设置器
对于需要验证或计算的属性,使用获取器和设置器:
javascript
// 好的做法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
get birthYear() {
return new Date().getFullYear() - this.age;
}
set age(newAge) {
if (typeof newAge === 'number' && newAge >= 0) {
this._age = newAge;
} else {
throw new Error('Age must be a non-negative number');
}
}
get age() {
return this._age;
}
}
// 不好的做法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
calculateBirthYear() {
return new Date().getFullYear() - this.age;
}
}5. 避免使用过多的静态方法
静态方法应该用于与类本身相关的操作,而不是与实例相关的操作:
javascript
// 好的做法
class MathUtil {
static add(a, b) {
return a + b;
}
}
// 不好的做法
class Person {
static greet(name) {
return `Hello, ${name}!`;
}
}类的常见错误
1. 忘记使用 new 关键字
创建类的实例时必须使用 new 关键字:
javascript
class Person {
constructor(name) {
this.name = name;
}
}
// 错误:忘记使用 new
// const person = Person('John'); // 错误:Class constructor Person cannot be invoked without 'new'
// 正确:使用 new
const person = new Person('John');
console.log(person.name); // 输出: John2. 忘记调用 super()
在子类的构造函数中,必须调用 super() 方法:
javascript
class Person {
constructor(name) {
this.name = name;
}
}
class Student extends Person {
constructor(name, grade) {
// 错误:忘记调用 super()
// this.grade = grade; // 错误:Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// 正确:先调用 super()
super(name);
this.grade = grade;
}
}
const student = new Student('John', 10);
console.log(student.name); // 输出: John
console.log(student.grade); // 输出: 103. 混淆实例方法和静态方法
实例方法和静态方法的调用方式不同:
javascript
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
static create(name) {
return new Person(name);
}
}
// 错误:实例不能调用静态方法
const person = new Person('John');
// console.log(person.create('Jane')); // 输出: undefined
// 正确:通过类调用静态方法
const person2 = Person.create('Jane');
console.log(person2.name); // 输出: Jane
// 错误:类不能调用实例方法
// console.log(Person.greet()); // 错误:Person.greet is not a function
// 正确:通过实例调用实例方法
console.log(person.greet()); // 输出: Hello, John!4. 类声明提升
类声明不会被提升,因此必须在定义类之后才能使用它:
javascript
// 错误:类声明不会被提升
// const person = new Person('John'); // 错误:Person is not defined
// 正确:在定义类之后使用
class Person {
constructor(name) {
this.name = name;
}
}
const person = new Person('John');
console.log(person.name); // 输出: John小结
类是 JavaScript 中用于创建对象的蓝图或模板,ES6 引入的 class 关键字使类的定义更加简洁和清晰。类支持构造函数、实例方法、静态方法、获取器和设置器等特性,还支持继承和方法重写。使用类可以实现面向对象编程、模块化和各种设计模式。在使用类时,应该遵循命名规范,合理使用继承,避免常见错误,以提高代码的可读性和可维护性。