JavaScript 中的类

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

我们之前也讨论了如何使用原型构造函数来实现这样的模型,并且 JavaScript 也提供了更接近经典面向对象概念的功能。

在本文中,我们将深入了解这些功能。值得记住的是,这里描述的功能并不是一种组合对象的新方法:在底层,它们仍然使用原型。它们只是一种更轻松地设置原型链的方式。

先决条件 对 HTML 和 CSS 的基本了解,熟悉 JavaScript 基础知识(参见第一步构建块)以及 OOJS 基础知识(参见对象简介对象原型面向对象编程)。
目标 了解如何使用 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 中用于编写面向对象程序的主要工具。我们在这里没有涵盖所有内容,但这应该足以帮助您入门。我们的关于类的文章是学习更多内容的好地方。