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>
的语法。
创建私有属性
假设我们希望之前定义的 name
和 age
属性是私有的。 我们可以通过以下方式重新定义这个类:
class Dog { #name = "Roy"; #age; walk () { console.log("Walking"); } }
如你所见,私有属性是通过使用井号(#
)作为前缀来声明的。 如果你尝试直接访问这些属性,将会遇到一个错误。
const dog = new Dog(); dog.#name
创建 Getter 和 Setter 方法
现在,类的 name
和 age
属性是私有的。 因此,它们只能通过类内部的方法来访问。
如果我们希望类外部的代码能够访问这些属性,我们可以定义 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();
创建构造函数方法
您还可以定义一个构造函数方法。 每当实例化一个新的类时,这个方法都会被自动调用。 构造函数方法可以用来初始化属性。 在此示例中,我们将使用构造函数来初始化 age
和 name
属性,使用用户在实例化期间提供的参数。
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 中类的概念。 我们讨论了类的定义、类可以包含的成员以及成员的不同分类。 然后,我们通过实际的示例展示了这些概念。
接下来,你可能对面向对象编程的面试问题感兴趣。