JavaScript 中的类

在上篇文章中,我们介绍了一些面向对象编程(OOP)的基本概念,并讨论了一个使用 OOP 原则模拟学校中的教授和学生的示例。

我们还讨论了如何使用 原型构造函数 来实现这样的模型,并且 JavaScript 也提供了更贴近经典 OOP 概念的特性。

在本文中,我们将介绍这些特性。需要注意的是,这里描述的特性并不是组合对象的新方式:底层仍然使用原型。它们只是使设置原型链更容易的一种方式。

预备知识 熟悉 JavaScript 基础(尤其是对象基础)和本模块先前课程中涵盖的面向对象 JavaScript 概念。
学习成果
  • 在 JavaScript 中创建类。
  • 在 JavaScript 中创建构造函数。
  • JavaScript 中的继承和封装。

类和构造函数

您可以使用 class 关键字声明一个类。这是我们上一篇文章中 Person 类的声明。

js
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 实例。

js
const giles = new Person("Giles");

giles.introduceSelf(); // Hi! I'm Giles

请注意,我们使用类的名称来调用构造函数,在本例中是 Person

省略构造函数

如果您不需要进行任何特殊初始化,可以省略构造函数,系统会为您生成一个默认构造函数。

js
class Animal {
  sleep() {
    console.log("zzzzzzz");
  }
}

const spot = new Animal();

spot.sleep(); // 'zzzzzzz'

继承

使用上面的 Person 类,让我们来定义 Professor 子类。

js
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,因此我们定义了一个构造函数,它接受 nameteaches 作为参数。此构造函数首先使用 super() 调用超类构造函数,并将 name 参数传递上去。超类构造函数负责设置 name。之后,Professor 构造函数设置 teaches 属性。

注意:如果子类有任何自己的初始化操作,它必须首先使用 super() 调用超类构造函数,并将超类构造函数期望的任何参数传递上去。

我们还重写了超类的 introduceSelf() 方法,并添加了一个新方法 grade() 来给论文评分(我们的教授不太好,只是给论文随机分配分数)。

使用此声明,我们现在可以创建和使用教授。

js
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 中实现封装。在上篇文章中,我们讨论了如何希望将 Studentyear 属性设为私有,以便我们可以更改射箭课程的规则而不破坏任何使用 Student 类的代码。

这是 Student 类的声明,实现了这一点。

js
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,浏览器会抛出错误。

js
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 语法限制放宽。

私有字段必须在类声明中声明,并且它们的名称以 # 开头。

私有方法

除了私有字段,您还可以拥有私有方法。与私有字段一样,私有方法的名称以 # 开头,并且只能由对象自己的方法调用。

js
class Example {
  somePublicMethod() {
    this.#somePrivateMethod();
  }

  #somePrivateMethod() {
    console.log("You called me?");
  }
}

const myExample = new Example();

myExample.somePublicMethod(); // 'You called me?'

myExample.#somePrivateMethod(); // SyntaxError

总结

在本文中,我们介绍了 JavaScript 中用于编写面向对象程序的各种主要工具。我们在这里没有涵盖所有内容,但这应该足以让您入门。我们关于 的文章是一个学习更多内容的绝佳途径。

接下来,我们将为您提供一些测试,您可以使用这些测试来检查您对我们迄今为止提供的面向对象 JavaScript 的理解和掌握程度。