JavaScript类
ES6引入了类(Class)语法,为JavaScript的面向对象编程提供了更简洁、更直观的语法糖。类语法基于原型继承机制,但提供了更接近传统面向对象语言的写法。
类的基本概念
类是面向对象编程的核心概念,它是创建对象的蓝图或模板,定义了对象的属性和方法。在JavaScript中,类语法是基于原型继承的语法糖,但提供了更清晰的语法结构。
类的定义
使用class关键字定义类,类名通常使用大驼峰命名法。
基本语法
// 类的基本定义
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
}
// 使用类创建实例
const person = new Person('Alice', 30);
person.greet(); // Hello, my name is Alice!
console.log(typeof Person); // function(类本质上是构造函数)
console.log(person instanceof Person); // true类表达式
类也可以使用表达式的形式定义,类似于函数表达式。
// 命名类表达式
const Person = class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
};
// 匿名类表达式
const AnotherPerson = class {
constructor(name) {
this.name = name;
}
};
const person = new Person('Bob', 25);
person.greet(); // Hello, my name is Bob!类的构造函数
构造函数是类中的特殊方法,用于初始化对象实例。使用constructor关键字定义,在使用new关键字创建实例时自动调用。
基本用法
class Car {
constructor(brand, model, year) {
this.brand = brand;
this.model = model;
this.year = year;
this.mileage = 0; // 初始化默认属性
}
drive(distance) {
this.mileage += distance;
console.log(`Drove ${distance} miles. Total mileage: ${this.mileage}`);
}
}
const myCar = new Car('Toyota', 'Camry', 2023);
myCar.drive(100); // Drove 100 miles. Total mileage: 100
myCar.drive(50); // Drove 50 miles. Total mileage: 150
console.log(myCar); // Car { brand: 'Toyota', model: 'Camry', year: 2023, mileage: 150 }默认参数
构造函数可以使用默认参数,与函数默认参数语法相同。
class Person {
constructor(name = 'Anonymous', age = 18) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
const person1 = new Person();
const person2 = new Person('Alice', 30);
person1.greet(); // Hello, my name is Anonymous and I'm 18 years old.
person2.greet(); // Hello, my name is Alice and I'm 30 years old.类的方法
类中可以定义多种类型的方法:
- 实例方法:实例可以调用的方法
- 静态方法:类本身调用的方法,使用
static关键字定义 - getter方法:用于获取属性值,使用
get关键字定义 - setter方法:用于设置属性值,使用
set关键字定义
实例方法
实例方法是类的实例可以调用的方法,定义在类的主体中。
class Circle {
constructor(radius) {
this.radius = radius;
}
// 实例方法
getArea() {
return Math.PI * this.radius * this.radius;
}
getCircumference() {
return 2 * Math.PI * this.radius;
}
}
const circle = new Circle(5);
console.log(circle.getArea()); // 78.53981633974483
console.log(circle.getCircumference()); // 31.41592653589793静态方法
静态方法是类本身调用的方法,使用static关键字定义,不能被实例调用。
class MathUtils {
// 静态方法
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static max(...numbers) {
return Math.max(...numbers);
}
}
// 调用静态方法
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.multiply(2, 3)); // 6
console.log(MathUtils.max(1, 3, 2, 5, 4)); // 5
// 实例不能调用静态方法
// const utils = new MathUtils();
// utils.add(2, 3); // TypeError: utils.add is not a functionGetter和Setter方法
Getter和Setter方法用于控制属性的访问和修改,可以在获取或设置属性时执行额外的逻辑。
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
// Getter方法
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
// Setter方法
set fullName(name) {
const [firstName, lastName] = name.split(' ');
this._firstName = firstName;
this._lastName = lastName;
}
}
const person = new Person('Alice', 'Smith');
console.log(person.fullName); // Alice Smith
// 使用setter设置属性
person.fullName = 'Bob Johnson';
console.log(person.fullName); // Bob Johnson
console.log(person._firstName); // Bob
console.log(person._lastName); // Johnson类的继承
使用extends关键字实现类的继承,子类可以继承父类的属性和方法,并可以重写或扩展父类的功能。
基本继承
// 父类
class Animal {
constructor(name) {
this.name = name;
this.type = 'Animal';
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}
// 子类,继承Animal
class Dog extends Animal {
constructor(name, breed) {
// 调用父类构造函数
super(name);
this.breed = breed;
this.type = 'Dog';
}
// 重写父类方法
makeSound() {
console.log(`${this.name} barks: Woof!`);
}
// 子类特有方法
fetch() {
console.log(`${this.name} is fetching!`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.makeSound(); // Buddy barks: Woof!
dog.fetch(); // Buddy is fetching!
console.log(dog.type); // Dog
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true调用父类方法
使用super关键字可以在子类中调用父类的方法。
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}
class Cat extends Animal {
makeSound() {
// 调用父类的makeSound方法
super.makeSound();
// 添加子类特有的逻辑
console.log(`${this.name} meows: Meow!`);
}
}
const cat = new Cat('Kitty');
cat.makeSound();
// 输出:
// Kitty makes a sound.
// Kitty meows: Meow!继承静态方法
子类会继承父类的静态方法。
class Parent {
static staticMethod() {
return 'Parent static method';
}
}
class Child extends Parent {
static childStaticMethod() {
// 调用父类的静态方法
return `${super.staticMethod()} - Child static method`;
}
}
console.log(Parent.staticMethod()); // Parent static method
console.log(Child.staticMethod()); // Parent static method(继承自父类)
console.log(Child.childStaticMethod()); // Parent static method - Child static method类的访问控制
ES2022引入了类的访问控制修饰符,用于控制属性和方法的访问权限:
- 公共(Public):默认访问级别,属性和方法可以在类的内部和外部访问
- 私有(Private):使用
#前缀标记,属性和方法只能在类的内部访问 - 受保护(Protected):使用
_前缀标记(约定俗成,不是语言级别的支持),属性和方法可以在类的内部和子类中访问
私有属性和方法
使用#前缀定义私有属性和方法,只能在类的内部访问。
class Person {
// 私有属性
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
// 公共方法访问私有属性
get name() {
return this.#name;
}
set name(newName) {
this.#name = newName;
}
// 私有方法
#validateAge(age) {
return age >= 0 && age <= 120;
}
set age(newAge) {
if (this.#validateAge(newAge)) {
this.#age = newAge;
} else {
console.error('Invalid age!');
}
}
get age() {
return this.#age;
}
}
const person = new Person('Alice', 30);
console.log(person.name); // Alice
person.name = 'Bob';
console.log(person.name); // Bob
person.age = 150; // Invalid age!
console.log(person.age); // 30
// 无法直接访问私有属性
// console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class
// person.#validateAge(20); // SyntaxError: Private method '#validateAge' must be declared in an enclosing class受保护属性和方法
JavaScript没有原生支持受保护属性和方法,通常使用_前缀约定,表示该属性或方法应该只在类的内部和子类中访问。
class Animal {
constructor(name) {
this._name = name; // 受保护属性
}
// 受保护方法
_makeSound() {
console.log(`${this._name} makes a sound.`);
}
// 公共方法
speak() {
this._makeSound();
}
}
class Dog extends Animal {
_makeSound() {
console.log(`${this._name} barks: Woof!`);
}
fetch() {
console.log(`${this._name} is fetching!`);
}
}
const dog = new Dog('Buddy');
dog.speak(); // Buddy barks: Woof!
dog.fetch(); // Buddy is fetching!
// 虽然可以访问,但按照约定不应该直接访问
console.log(dog._name); // Buddy类的最佳实践
- 使用大驼峰命名法:类名使用大驼峰命名法,如
Person、Car。 - 使用构造函数初始化属性:在构造函数中初始化所有必要的属性。
- 合理使用访问控制:使用私有属性保护内部状态,提供公共方法访问和修改属性。
- 避免过度继承:继承层次不宜过深,建议不超过3层。
- 优先使用组合而非继承:当需要复用代码时,优先考虑组合而非继承。
- 使用静态方法处理类级别的逻辑:将与实例无关的逻辑放在静态方法中。
- 使用getter和setter控制属性访问:在需要时使用getter和setter控制属性的访问和修改。
- 避免在构造函数中执行复杂逻辑:构造函数应该简洁,只负责初始化属性。
类与原型的关系
虽然类语法提供了更清晰的面向对象写法,但底层仍然基于原型继承机制。
类与构造函数的对应关系
// ES6类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
}
// 等价于ES5构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}!`);
};
// 检查原型
const person = new Person('Alice', 30);
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
console.log(person.__proto__ === Person.prototype); // true类继承与原型链的对应关系
// ES6类继承
class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
makeSound() {
console.log(`${this.name} barks: Woof!`);
}
}
// 等价于ES5组合继承
function Animal(name) {
this.name = name;
}
Animal.prototype.makeSound = function() {
console.log(`${this.name} makes a sound.`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.makeSound = function() {
console.log(`${this.name} barks: Woof!`);
};类的应用场景
1. 创建复杂对象
当需要创建具有相似结构和行为的复杂对象时,使用类可以提高代码的可维护性和复用性。
class User {
constructor(id, username, email) {
this.id = id;
this.username = username;
this.email = email;
this.createdAt = new Date();
this.posts = [];
}
addPost(title, content) {
const post = new Post(title, content, this.id);
this.posts.push(post);
return post;
}
}
class Post {
constructor(title, content, authorId) {
this.id = Date.now();
this.title = title;
this.content = content;
this.authorId = authorId;
this.createdAt = new Date();
this.comments = [];
}
addComment(content, authorId) {
const comment = new Comment(content, authorId, this.id);
this.comments.push(comment);
return comment;
}
}
class Comment {
constructor(content, authorId, postId) {
this.id = Date.now();
this.content = content;
this.authorId = authorId;
this.postId = postId;
this.createdAt = new Date();
}
}
// 使用类创建对象
const user = new User(1, 'alice', 'alice@example.com');
const post = user.addPost('Hello World', 'This is my first post.');
const comment = post.addComment('Great post!', 2);
console.log(user);
console.log(post);
console.log(comment);2. 实现设计模式
类非常适合实现各种设计模式,如工厂模式、单例模式、观察者模式等。
// 单例模式示例
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = [];
Singleton.instance = this;
}
add(item) {
this.data.push(item);
}
getItems() {
return this.data;
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 创建单例实例
const instance1 = new Singleton();
const instance2 = new Singleton();
const instance3 = Singleton.getInstance();
console.log(instance1 === instance2); // true
console.log(instance1 === instance3); // true
instance1.add('item1');
console.log(instance2.getItems()); // ['item1']总结
ES6类语法为JavaScript的面向对象编程提供了更清晰、更直观的语法,虽然底层仍然基于原型继承,但提供了更接近传统面向对象语言的写法。
- 类使用
class关键字定义,包括构造函数、实例方法、静态方法、getter和setter方法 - 使用
extends关键字实现继承,子类可以继承父类的属性和方法 - 使用
super关键字调用父类的构造函数和方法 - 使用
#前缀定义私有属性和方法,只能在类的内部访问 - 类本质上是构造函数,继承基于原型链实现
掌握类语法是现代JavaScript开发的重要技能,它可以帮助我们编写更清晰、更可维护的面向对象代码。