JavaScript 中的类
在 上一篇文章中,我们介绍了面向对象编程 (OOP) 的一些基本概念,并讨论了一个使用 OOP 原则对学校中的教授和学生进行建模的示例。
我们之前也讨论了如何使用原型和构造函数来实现这样的模型,并且 JavaScript 也提供了更接近经典面向对象概念的功能。
在本文中,我们将深入了解这些功能。值得记住的是,这里描述的功能并不是一种组合对象的新方法:在底层,它们仍然使用原型。它们只是一种更轻松地设置原型链的方式。
类和构造函数
您可以使用class
关键字声明一个类。以下是我们上一篇文章中Person
类的声明
class Person {
name;
constructor(name) {
this.name = name;
}
introduceSelf() {
console.log(`Hi! I'm ${this.name}`);
}
}
这声明了一个名为Person
的类,它包含:
- 一个
name
属性。 - 一个构造函数,它接收一个
name
参数,用于初始化新对象的name
属性。 - 一个
introduceSelf()
方法,可以使用this
引用对象的属性。
name;
声明是可选的:您可以省略它,构造函数中的this.name = name;
行将在初始化之前创建name
属性。但是,在类声明中显式列出属性可能会使阅读代码的人更容易了解哪些属性属于此类。
您也可以在声明属性时将其初始化为默认值,例如name = '';
。
构造函数使用constructor
关键字定义。就像类定义之外的构造函数一样,它将:
- 创建一个新对象。
- 将
this
绑定到新对象,以便您可以在构造函数代码中引用this
。 - 运行构造函数中的代码。
- 返回新对象。
给定上面的类声明代码,您可以像这样创建和使用新的Person
实例:
const giles = new Person("Giles");
giles.introduceSelf(); // Hi! I'm Giles
请注意,我们使用类的名称(本例中为Person
)来调用构造函数。
省略构造函数
如果您不需要进行任何特殊的初始化,则可以省略构造函数,系统会为您生成一个默认构造函数。
class Animal {
sleep() {
console.log("zzzzzzz");
}
}
const spot = new Animal();
spot.sleep(); // 'zzzzzzz'
继承
给定我们上面的Person
类,让我们定义Professor
子类。
class Professor extends Person {
teaches;
constructor(name, teaches) {
super(name);
this.teaches = teaches;
}
introduceSelf() {
console.log(
`My name is ${this.name}, and I will be your ${this.teaches} professor.`,
);
}
grade(paper) {
const grade = Math.floor(Math.random() * (5 - 1) + 1);
console.log(grade);
}
}
我们使用extends
关键字来说明此类继承自另一个类。
Professor
类添加了一个新的属性teaches
,因此我们声明它。
由于我们希望在创建新的Professor
时设置teaches
,因此我们定义了一个构造函数,它将name
和teaches
作为参数。此构造函数首先执行的操作是使用super()
调用超类构造函数,并将name
参数传递给它。超类构造函数负责设置name
。之后,Professor
构造函数设置teaches
属性。
注意:如果子类需要进行任何自己的初始化,则**必须**首先使用super()
调用超类构造函数,并将超类构造函数期望的任何参数传递给它。
我们还覆盖了超类的introduceSelf()
方法,并添加了一个新的方法grade()
,用于给论文评分(我们的教授不太好,只是给论文随机打分)。
通过此声明,我们现在可以创建和使用教授了。
const walsh = new Professor("Walsh", "Psychology");
walsh.introduceSelf(); // 'My name is Walsh, and I will be your Psychology professor'
walsh.grade("my paper"); // some random grade
封装
最后,让我们看看如何在 JavaScript 中实现封装。在上一篇文章中,我们讨论了如何希望将Student
的year
属性设为私有,以便我们可以更改关于射箭课程的规则,而不会破坏任何使用Student
类的代码。
以下是执行此操作的Student
类声明:
class Student extends Person {
#year;
constructor(name, year) {
super(name);
this.#year = year;
}
introduceSelf() {
console.log(`Hi! I'm ${this.name}, and I'm in year ${this.#year}.`);
}
canStudyArchery() {
return this.#year > 1;
}
}
在此类声明中,#year
是一个私有数据属性。我们可以构造一个Student
对象,它可以在内部使用#year
,但是如果对象外部的代码尝试访问#year
,浏览器将抛出错误。
const summers = new Student("Summers", 2);
summers.introduceSelf(); // Hi! I'm Summers, and I'm in year 2.
summers.canStudyArchery(); // true
summers.#year; // SyntaxError
注意:在 Chrome 控制台中运行的代码可以在类外部访问私有属性。这是 DevTools 对 JavaScript 语法限制的唯一放松。
私有数据属性必须在类声明中声明,并且它们的名称以#
开头。
私有方法
除了私有数据属性之外,您还可以拥有私有方法。与私有数据属性一样,它们的名称以#
开头,并且只能由对象自己的方法调用。
class Example {
somePublicMethod() {
this.#somePrivateMethod();
}
#somePrivateMethod() {
console.log("You called me?");
}
}
const myExample = new Example();
myExample.somePublicMethod(); // 'You called me?'
myExample.#somePrivateMethod(); // SyntaxError
测试你的技能!
您已阅读到本文的结尾,但您还记得最重要的信息吗?在继续之前,您可以进行一些进一步的测试来验证您是否保留了这些信息——请参阅测试您的技能:面向对象 JavaScript。