JavaScript 中的类
在 上一篇文章 中,我们介绍了面向对象编程(OOP)的一些基本概念,并讨论了一个使用 OOP 原理为学校教授和学生建模的例子。
¥In the last article, we introduced some basic concepts of object-oriented programming (OOP), and discussed an example where we used OOP principles to model professors and students in a school.
我们还讨论了如何使用 prototypes 和 constructors 来实现这样的模型,并且 JavaScript 还提供了更接近经典 OOP 概念的功能。
¥We also talked about how it's possible to use prototypes and constructors to implement a model like this, and that JavaScript also provides features that map more closely to classical OOP concepts.
在本文中,我们将介绍这些功能。值得记住的是,这里描述的功能并不是组合对象的新方法:在幕后,他们仍然使用原型。它们只是一种让建立原型链变得更容易的方法。
¥In this article, we'll go through these features. It's worth keeping in mind that the features described here are not a new way of combining objects: under the hood, they still use prototypes. They're just a way to make it easier to set up a prototype chain.
Prerequisites: | A basic understanding of HTML and CSS, familiarity with JavaScript basics (see First steps and Building blocks) and OOJS basics (see Introduction to objects, Object prototypes, and Object-oriented programming). |
---|---|
Objective: | To understand how to use the features JavaScript provides to implement "classical" object-oriented programs. |
类和构造函数
¥Classes and constructors
你可以使用 class
关键字声明一个类。这是上一篇文章中 Person
的类声明:
¥You can declare a class using the class
keyword. Here's a class declaration for our Person
from the previous article:
class Person {
name;
constructor(name) {
this.name = name;
}
introduceSelf() {
console.log(`Hi! I'm ${this.name}`);
}
}
这声明了一个名为 Person
的类,其中:
¥This declares a class called Person
, with:
name
属性。- 一个带有
name
参数的构造函数,用于初始化新对象的name
属性 - 可以使用
this
引用对象属性的introduceSelf()
方法。
name;
声明是可选的:你可以省略它,构造函数中的 this.name = name;
行将在初始化之前创建 name
属性。但是,在类声明中显式列出属性可能会让阅读代码的人更容易了解哪些属性属于此类。
¥The name;
declaration is optional: you could omit it, and the line this.name = name;
in the constructor will create the name
property before initializing it. However, listing properties explicitly in the class declaration might make it easier for people reading your code to see which properties are part of this class.
你还可以在声明属性时使用诸如 name = '';
之类的行将属性初始化为默认值。
¥You could also initialize the property to a default value when you declare it, with a line like name = '';
.
构造函数是使用 constructor
关键字定义的。就像 类定义之外的构造函数 一样,它将:
¥The constructor is defined using the constructor
keyword. Just like a constructor outside a class definition, it will:
- 创建一个新对象
- 将
this
绑定到新对象,这样你就可以在构造函数代码中引用this
- 运行构造函数中的代码
- 返回新对象。
给定上面的类声明代码,你可以创建并使用一个新的 Person
实例,如下所示:
¥Given the class declaration code above, you can create and use a new Person
instance like this:
const giles = new Person("Giles");
giles.introduceSelf(); // Hi! I'm Giles
请注意,我们使用类名称(本例中为 Person
)调用构造函数。
¥Note that we call the constructor using the name of the class, Person
in this example.
省略构造函数
¥Omitting constructors
如果你不需要做任何特殊的初始化,你可以省略构造函数,系统会为你生成一个默认的构造函数:
¥If you don't need to do any special initialization, you can omit the constructor, and a default constructor will be generated for you:
class Animal {
sleep() {
console.log("zzzzzzz");
}
}
const spot = new Animal();
spot.sleep(); // 'zzzzzzz'
遗产
¥Inheritance
给定上面的 Person
类,让我们定义 Professor
子类。
¥Given our Person
class above, let's define the Professor
subclass.
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
关键字来表示这个类继承自另一个类。
¥We use the extends
keyword to say that this class inherits from another class.
Professor
类添加了一个新属性 teaches
,因此我们声明它。
¥The Professor
class adds a new property teaches
, so we declare that.
由于我们希望在创建新的 Professor
时设置 teaches
,因此我们定义了一个构造函数,它将 name
和 teaches
作为参数。该构造函数所做的第一件事是使用 super()
调用超类构造函数,并传递 name
参数。超类构造函数负责设置 name
。之后,Professor
构造函数设置 teaches
属性。
¥Since we want to set teaches
when a new Professor
is created, we define a constructor, which takes the name
and teaches
as arguments. The first thing this constructor does is call the superclass constructor using super()
, passing up the name
parameter. The superclass constructor takes care of setting name
. After that, the Professor
constructor sets the teaches
property.
注意:如果子类需要进行任何自己的初始化,则它必须首先使用
super()
调用超类构造函数,并传递超类构造函数所需的任何参数。¥Note: If a subclass has any of its own initialization to do, it must first call the superclass constructor using
super()
, passing up any parameters that the superclass constructor is expecting.
我们还重写了超类中的 introduceSelf()
方法,并添加了一个新方法 grade()
,用于对论文进行评分(我们的教授不是很好,只是为论文分配随机评分)。
¥We've also overridden the introduceSelf()
method from the superclass, and added a new method grade()
, to grade a paper (our professor isn't very good, and just assigns random grades to papers).
通过这个声明,我们现在可以创建和使用教授:
¥With this declaration we can now create and use professors:
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
封装
¥Encapsulation
最后,我们来看看如何在 JavaScript 中实现封装。在上一篇文章中,我们讨论了如何将 Student
的 year
属性设为私有,这样我们就可以更改有关 archery 类的规则,而不会破坏任何使用 Student
类的代码。
¥Finally, let's see how to implement encapsulation in JavaScript. In the last article we discussed how we would like to make the year
property of Student
private, so we could change the rules about archery classes without breaking any code that uses the Student
class.
下面是 Student
类的声明,它就是这样做的:
¥Here's a declaration of the Student
class that does just that:
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
,浏览器会抛出错误:
¥In this class declaration, #year
is a private data property. We can construct a Student
object, and it can use #year
internally, but if code outside the object tries to access #year
the browser throws an error:
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 控制台中运行的代码可以访问类外部的私有属性。这是对 JavaScript 语法限制的仅限 DevTools 的放宽。
¥Note: Code run in the Chrome console can access private properties outside the class. This is a DevTools-only relaxation of the JavaScript syntax restriction.
私有数据属性必须在类声明中声明,其名称以 #
开头。
¥Private data properties must be declared in the class declaration, and their names start with #
.
私有方法
¥Private methods
你可以拥有私有方法以及私有数据属性。就像私有数据属性一样,它们的名称以 #
开头,并且只能由对象自己的方法调用:
¥You can have private methods as well as private data properties. Just like private data properties, their names start with #
, and they can only be called by the object's own methods:
class Example {
somePublicMethod() {
this.#somePrivateMethod();
}
#somePrivateMethod() {
console.log("You called me?");
}
}
const myExample = new Example();
myExample.somePublicMethod(); // 'You called me?'
myExample.#somePrivateMethod(); // SyntaxError
测试你的技能!
¥Test your skills!
你已读完本文,但你还记得最重要的信息吗?在继续之前,你可以找到一些进一步的测试来验证你是否已保留此信息 - 请参阅 测试你的技能:面向对象的 JavaScript。
¥You've reached the end of this article, but can you remember the most important information? You can find some further tests to verify that you've retained this information before you move on — see Test your skills: Object-oriented JavaScript.
概括
¥Summary
在本文中,我们介绍了 JavaScript 中用于编写面向对象程序的主要工具。我们没有在这里介绍所有内容,但这应该足以让你开始。我们的 关于类的文章 是一个了解更多信息的好地方。
¥In this article, we've gone through the main tools available in JavaScript for writing object-oriented programs. We haven't covered everything here, but this should be enough to get you started. Our article on Classes is a good place to learn more.