类的简介
类是用于创建对象的模板,这与其他面向对象编程语言的概念一样。但是,在 JavaScript 中,类实际上是特殊的函数,就像你能够定义的函数表达式和函数声明一样。
构造函数
构造函数是一种专门用于初始化新对象的函数。使用 new 关键字调用构造函数会自动创建新对象,因此构造函数本身只需要初始化新对象的属性。构造函数的 prototype 将被作为新对象的原型的属性。
function Person(name, age) {this.name = namethis.age = age}Person.prototype = {run() {return \'run...\'},eat() {return \'eat...\'}}let xiaohong = new Person(\'小红\', 11)let xiaoming = new Person(\'小明\', 10)console.dir(Person) // 输出构造函数console.dir(xiaohong) // 输出实例化对象
构造函数 Person() 的 prototype 被作为新对象的原型的属性。
this 关键字
在构造器初始化属性时,赋值表达式左侧是 this.name ,this 指向的是实例化对象本身。
function Person(name, age) {this.name = namethis.age = ageconsole.dir(this) // 打印this}let xiaohong = new Person(\'小红\', 11)console.log(xiaohong) // 打印实例化对象xiaohong
this 指向的是实例化对象 xiaohong。
class 关键字
自 ES6 起,引入了 class 关键字用于创建类。
class Person {constructor(name, age) {this.name = namethis.age = age}run() {return \'run...\'}eat() {return \'eat...\'}}let xiaohong = new Person(\'小红\', 11)let xiaoming = new Person(\'小明\', 10)console.dir(Person) // 输出构造函数console.dir(xiaohong) // 输出实例化对象
在类中定义的方法,会自动将方法定义在它的原型中。这与上一小节利用构造函数定义的类没有任何区别。因为 class 关键字并未改 JavaScript 类基于原型的本质,它只是定义类的“语法糖”。
static 关键字
静态属性和静态方法属于类,而不属于任何单个对象。在 JavaScript 中,静态属性和静态方法是在构造函数而非实例对象上定义的,所以静态属性和静态方法也称之为类属性和类方法。
静态方法和属性
普通方法和普通属性属于所有实例对象,所以只能以实例对象来调用。静态方法和静态属性是所有对象共享的,所以只能由构造函数名(或类名)来调用。
function Person(name, age) {this.name = namethis.age = age}// 定义静态方法runPerson.run = function run() {console.log(\'run...\')}// 定义静态属性typePerson.type = \'人类\'let p = new Person(\'小明\', 12)Person.run() // 调用静态方法runPerson.type // 调用静态属性type => \'人类\'
因为静态属性和静态方法是定义在构造函数之上的,所以 Person 的原型 constructor 属性下有静态方法 run 和静态属性 type,而不是 Person 实例对象上的,该实例对象只有 age 和 name 这两个非共享(独立的)的属性。
下面将利用 class 关键字定义类。在 class 关键字定义的类中,只需要在普通方法和属性前加上 static 关键字。
class Person {static type = \'人类\'constructor(name, age) {this.name = namethis.age = age}run() {console.log(\'normal run...\')}static run() {console.log(\'static run...\')}}let p = new Person(\'小明\', 12)Person.run() // 调用静态方法runPerson.type // 调用静态属性type => \'人类\'
私有属性
类属性在默认情况下是公共的,可以被外部类检测或修改。在ES2020 实验草案中,增加了定义私有类字段的能力,写法是使用一个 # 作为前缀。
class Person {#nameget name() {return this.#name}set name(name) {this.#name = name}}
私有属性在 Java 中被体现得尤为突出。通常类的属性会被加上 private 使其私有化。这样做的目的是防止外部访问时,意外地修改属性。并且会有一对对应的 getter 和 setter 来操作私有属性。
子类
在面向对象编程中,类 B 可以继承类 A,类 A 是父类,类 B 是子类。类 B 继承类 A 的方法,类 B 也可以定义自己的方法。
在 ES6 及以后,要继承父类,可以简单地在类声明中加上一个extends子句,甚至对内置的类也可以这样。
class EZArray extends Array {get first() { return this[0] }get last() { return this[this.length - 1] }}let a = new EZArray()a instanceof EZArray // true,a是子类的实例a instanceof Array // true,a是父类的实例a.push(1, 2, 3, 4) // 子类继承的父类的方法a.first() // 子类定义的方法a[1] // 数组的属性访问表达式仍然有效Array.isArray(a) // true,子类的实例是数组EZArray.isArray(a) // true,子类继承了静态方法
案例来源于《JavaScript权威指南》- 第9章 – 216页。
一个类使用 extends 继承父类,那么子类的构造函数必须使用 super() 调用父类构造函数。如果在子类中没有定义构造函数,解释器会自动创建一个。
class NormalMap extends Map {} // 未定义构造函数,解释器自动创建let normalMap = new NormalMap()class UnnormalMap extends Map { // 定义构造函数,但未调用super()constructor(keyType, valueType) {this.keyType = keyTypethis.valueType = valueType}}let unnormalMap = new UnnormalMap()
UnnormalMap 子类在构造函数中未调用 super() ,因此抛出 ReferenceError 异常。在调用了 super() 之后,一定要确保它在 this 关键字之前,否则也会抛出异常。