带有示例的完整指南

JavaScript 是一种支持多种编程范式的语言,它允许开发者采用函数式、面向对象以及命令式等不同的编程风格来构建程序。

为了更好地支持面向对象的编程模式,JavaScript 引入了类的概念。理解类的工作原理至关重要,因此本文将作为一份指南,深入探讨 JavaScript 中类的定义以及如何有效利用它们。

JavaScript 中的类是什么?

在面向对象编程(OOP)的世界中,我们将复杂的系统视为相互协作的对象集合。 为了实现这一目标,对象会利用属性来存储数据,并通过定义好的方法来执行相应的操作。 类,作为对象的蓝图,详细描述了同一类型对象所包含的属性和方法。 因此,我们可以将类视为创建对象的模板。

类中使用的术语

为了确保我们对类的理解达成一致,这里将对类进行详细的描述,并介绍在本文中将会使用的关键术语。如果您对面向对象编程已经有所了解,可以直接跳到下一部分。

✅ 类是对象的蓝图,它提供了一个模板,用于创建特定类型的对象。 通过类定义的模板创建对象的过程,我们称之为实例化。

✅ 类成员是指属于类的所有组成部分。 类成员主要分为两种:方法和属性。

✅ 属性是一种类成员,其主要功能是存储数据值。这些值可以是简单的,如数字和字符串,也可以是复杂的数据结构,比如对象和数组。

✅ 有些属性只能在类的内部被访问,我们称之为私有属性。而另一些属性则可以在类的外部访问,这些属性被称为公共属性。

✅ 方法是在类内部定义的函数。它们属于类的一部分,可以访问类中的公共和私有属性。 与属性类似,方法也分为公共方法和私有方法。

✅ 为了使类外部的代码能够与类内部的属性进行交互,我们通常会使用两种特殊的方法:getter 和 setter。Getter 用于获取类属性的值,而 setter 用于设置类属性的值。

✅ 还有一些成员被定义为静态的。这意味着它们只能通过类本身访问,而不能通过类的实例访问。

与之相对的,非静态的类成员只能通过类的实例来访问。这意味着你需要先创建一个类的实例,才能使用这些成员。

当一个类被实例化时,一个特殊的方法会被调用,用于设置实例的属性,这个方法就是构造函数。

实例化类详解

在 JavaScript 中,我们使用 new 关键字加上类名来实例化一个类。 让我们用 Array 类来举个例子:

const myArr = new Array()

在 JavaScript 中创建类

本节将详细讲解如何创建类,并逐步实现我们在术语部分所定义的类。 我们会通过一系列的示例来进行讲解,每一个示例都会在前一个示例的基础上进行扩展。

声明一个空类

在 JavaScript 中声明类,我们需要使用 class 关键字,并为类命名。 接下来,我们需要定义类的主体部分。 类的主体用大括号包围,其中包含了所有的类成员。

下面是一个带有空主体的类声明示例:

class Dog {

}

现在,您可以按照以下方式实例化该类并打印出来。

const pet = new Dog;
console.log(pet);

创建公共属性

公共属性是通过标识符以及一个可选的初始值进行定义的。

class Dog {
    name = "Roy";
    age;
}

在这里,我们定义了一个名为 name 的属性,并给它赋予了一个字符串值 “Roy”,以及一个名为 age 的属性,但未对其进行初始化。

const pet = new Dog();

console.log(pet.name);
console.log(pet.age);

定义公共方法

我们可以在类的主体中添加方法。 我们定义方法的方式与定义函数的方式类似,但是,我们省略了 function 关键字。

class Dog {
    name = "Roy";
    age;

    walk () {
        console.log("Walking");
    }
}

在上面的例子中,我们定义了一个名为 walk 的方法。 Dog 类的每个实例都将拥有这个方法。

const pet = new Dog();
pet.walk();

从方法访问属性

在 JavaScript 中,我们通常使用点运算符来访问对象的属性。 例如,如果我们有一个名为 person 的对象,并且想要访问它的 name 属性,我们可以这样做:

person.name

但是,如果我们想从对象内部访问属性,我们可以使用 this 关键字,而不是使用对象名称。 这是一个例子:

this.name

this 关键字指的是当前对象。 因此,如果想从类的方法中访问类的属性,我们需要使用 this.<property_name> 的语法。

创建私有属性

假设我们希望之前定义的 nameage 属性是私有的。 我们可以通过以下方式重新定义这个类:

class Dog {
    #name = "Roy";
    #age;

    walk () {
        console.log("Walking");
    }
}

如你所见,私有属性是通过使用井号(#)作为前缀来声明的。 如果你尝试直接访问这些属性,将会遇到一个错误。

const dog = new Dog();

dog.#name

创建 Getter 和 Setter 方法

现在,类的 nameage 属性是私有的。 因此,它们只能通过类内部的方法来访问。

如果我们希望类外部的代码能够访问这些属性,我们可以定义 getter 和 setter 方法。 让我们对 name 属性执行此操作。

class Dog {
    #name = "Roy";
    #age;

    get name () {
        return this.#name;
    }

    set name (value) {
        this.#name = value;
    }

    walk () {
        console.log("Walking");
    }
}

通过上面定义的类,您可以使用以下代码来设置名称并显示它:

const pet = new Dog();

// Setting the name
pet.name = "Rex";

// Getting the name
console.log(pet.name);

创建私有方法

和私有属性一样,私有方法也以井号为前缀。 因此,声明一个私有方法的方式如下:

class Dog {
    #name = "Roy";
    #age;

    get name () {
        return this.#name;
    }

    set name (value) {
        this.#name = value;
    }

    #increaseAge() {
        this.#age ++;
    }

    #decreaseAge () {
        this.#age --;
    }

    walk () {
        console.log("Walking");
    }
}

如果您尝试从类外部访问这些方法,将会导致错误。

const pet = new Dog();
pet.#increaseAge();

创建构造函数方法

您还可以定义一个构造函数方法。 每当实例化一个新的类时,这个方法都会被自动调用。 构造函数方法可以用来初始化属性。 在此示例中,我们将使用构造函数来初始化 agename 属性,使用用户在实例化期间提供的参数。

class Dog {
    #name;
    #age;

    constructor (name = "Dog", age = 0) {
        this.#name = name;
        this.#age = age;
    }

    get name () {
        return this.#name;
    }

    set name (value) {
        this.#name = value;
    }

    #increaseAge() {
        this.#age ++;
    }

    #decreaseAge () {
        this.#age --;
    }

    walk () {
        console.log("Walking");
    }
}

当我们实例化我们的类时,我们可以传入姓名和年龄。

const pet = new Dog('Roy', 3);
console.log(pet.name);

创建静态属性和方法

正如前面提到的,静态成员可以在不实例化类的情况下直接访问。 在下面的示例中,我们将创建一个静态属性和一个静态方法。

class Dog {
    #name;
    #age;
    static genus = "Canis";

    constructor (name = "Dog", age = 0) {
        this.#name = name;
        this.#age = age;
    }

    static bark() {
        console.log("Woof");
    }

    get name () {
        return this.#name;
    }

    set name (value) {
        this.#name = value;
    }

    #increaseAge() {
        this.#age ++;
    }

    #decreaseAge () {
        this.#age --;
    }

    walk () {
        console.log("Walking");
    }
}

现在,您可以直接通过类名访问静态属性和方法,而无需实例化该类。

console.log(Dog.genus);
Dog.bark();

继承

类可以从其他类继承属性。 从另一个类继承成员的类称为子类,而被继承的类称为父类或基类。

在 JavaScript 中创建一个子类,我们需要使用 extends 关键字。 这是一个子类继承自 Dog 类的例子:

class Rottweiler extends Dog {
    constructor (name, age) {
        super(name, age);
        this.breed = 'rottweiler';
    }
}

正如你所看到的,这个类的结构和之前的基本一致。 但是,在构造函数内部,我们调用了 super 函数。 super 关键字引用了父类的构造函数。 因此,我们在子类中调用父类的构造函数,并传入姓名和年龄。

const myPet = new Rottweiler();
console.log(myPet);

结论

在本文中,我们深入探讨了 JavaScript 中类的概念。 我们讨论了类的定义、类可以包含的成员以及成员的不同分类。 然后,我们通过实际的示例展示了这些概念。

接下来,你可能对面向对象编程的面试问题感兴趣。