类和继承

编程是经常需要自定义类型的。

类型是 具有共同特征(属性、行为)的对象 的 特性 的定义。

比如 所有的汽车(不管什么车)都有 轮子, 所有的 汽油车 都有发动机 , 所有的电动车都有电池。

我们定义 汽车、汽油车、电动车 这些类型 时,就是定义它的 特征的集合


前面的学习,大家发现

js中对象种类的共同特性 的提取定义, 是 通过 原型对象(包括构造函数) 实现的。

比如,我们程序要创建各种类型的汽车对象(电动车类型、汽油车类型),然后再创建具体的实例

通常是这样:

// 汽车类型
function Car(price, owner) {
  this.price = price
  this.owner = owner
}
// prototype  对象里面添加属性
Car.prototype.type = '汽车'
Car.prototype.hasTire = true
Car.prototype.showInfo = function (){
    console.log(`车型 :${this.type} -  车主 : ${this.owner} - 售价 : ${this.price} `)
}

// 电动车类型
function ElectricCar(price, owner) {
  Car.call(this, price, owner)
}
ElectricCar.prototype = Object.create(Car.prototype);
ElectricCar.prototype.type = '电动车'
ElectricCar.prototype.hasBattery = true
ElectricCar.prototype.hasEngine  = false

// 汽油车类型
function GasolineCar(price, owner) {
  Car.call(this, price, owner)
}
GasolineCar.prototype = Object.create(Car.prototype);
GasolineCar.prototype.type = '汽油车'
GasolineCar.prototype.hasBattery = false
GasolineCar.prototype.hasEngine  = true

// 创建实例
var myCar1 = new ElectricCar(300000, '白月黑羽')

var myCar2 = new GasolineCar(350000, '白月黑羽')

myCar1.showInfo()
myCar2.showInfo()

如果你以前学习过 Python 、Java 等从类创建 对象的语言,

这种继承关系的语法 可能多少会觉得不习惯,不好记。


所以, ES6 给 js 引入了 的概念

类的定义和实例化

js 中可以这样 自定义对象类型

// 定义类
class Car {    
    type = '汽车'
    hasTire = true

    constructor(price, owner) {
        this.price = price
        this.owner = owner
    }

    showInfo(){
        console.log(`车型 :${this.type} -  车主 : ${this.owner} - 售价 : ${this.price} `)
    }
}   

// 创建实例对象
var myCar1 = new Car(300000, '白月黑羽')
var myCar2 = new Car(350000, '白月黑羽')

console.log(myCar1.type, myCar1.hasTire,myCar1.price,myCar1.owner)
console.log(myCar2.type, myCar2.hasTire,myCar2.price,myCar2.owner)

myCar1.showInfo()
myCar2.showInfo()

定义类使用关键字 class 后面加 类的名称。

类名的规范 和 变量命名规范一样。 通常我们会把类名 首字母大写, 这里定义的类名就是 Car


紧跟类名 后面的花括号里面的所有内容就是 类定义,是该类的所有 属性(方法)的定义。


从定义的类来 创建对象 也是使用 new 操作符,后面加上类名和括号,括号里面的参数是传递给 类定义里面 名为 constructor 的方法的 。

constructor 被称之为构造函数, 作用类似前面讲的独立构造函数。

这个方法在 使用 new 创建这个类 对应的 对象时,会被解释器自动调用 ,并且传入new 语句里面的参数。


类里面定义的函数(包括constructor) 可以叫类的方法, 都是 不需要 function 关键字 声明的


通过类 创建的对象 称之为 这个类的一个 实例


前面,我们说过 构造函数 也可以看作类型, 构造产生的对象 ,就是 这个构造函数类型的实例。

同样的, 这里通过类产生的对象,就是这个 类的实例

比如,上面的例子中:

myCar1 就是 Car 类型的实例

myCar2 也是 Car 类型的实例

// 结果是 true
myCar1 instanceof Car

// 结果是 true
myCar2 instanceof Car

类的继承关系

子类继承父类

真实世界中,类型之间 可能存在 范围 包含关系。

比如: 这个类型 和 电动车 这个类型。

是包括了 电动车 的。 如果 一辆车 是一个 电动车,那么它必定是一个

这种关系,编程语言中称之为 继承关系

比如上面的例子, 电动车 这个类 就 继承 这个类。

通常我们把被继承的类称之为 父类 或者 基类 或者 超类

把继承类称之为 子类 或者 派生类


js 中 类的继承关系可以通过关键字 extend

如下:

// 父类
class Car {    
    type = '汽车'
    hasTire = true

    constructor(price, owner) {
        this.price = price
        this.owner = owner
    }

    showInfo(){
        console.log(`车型 :${this.type} -  车主 : ${this.owner} - 售价 : ${this.price} `)
    }
}
    
// 子类
class ElectricCar extends Car {
    type = '电动车'
    hasBattery = true
    hasEngine  = false
}

class GasolineCar extends Car {
    type = '汽油车'
    hasBattery = false
    hasEngine  = true
}


// 创建实例
var myCar1 = new ElectricCar(300000, '白月黑羽')
var myCar2 = new GasolineCar(350000, '白月黑羽')

myCar1.showInfo()
myCar2.showInfo()

以一个类 为父类(原型) , 创建新类 使用关键字 extend , 通常这样的新类型 称之为 子类

比如,上例中, ElectricCar 和 GasolineCar 都是 Car 类型 的子类。

myCar1 就是 ElectricCar 类型的实例,同样也是 Car 类型的实例

myCar2 就是 GasolineCar 类型的实例,同样也是 Car 类型的实例

// 结果是 true
myCar1 instanceof ElectricCar

// 结果是 true
myCar1 instanceof Car

// 结果是 false
myCar1 instanceof GasolineCar

// 结果是 true
myCar2 instanceof GasolineCar

我们通常会说子类 继承 了 父类, 继承表明 类型之间 的 范围包含 关系


大家注意: 子类会自动拥有父类的一切属性和方法

为什么? 因为一个子类的实例对象 ,必定也是一个父类的实例对象。 当然需要拥有父类的一切属性和方法。

就像 一个电动车 当然应该 拥有一个 所应该具有的一切特性。

比如

// showInfo 是父类 Car 的方法,也可以使用
myCar1.showInfo()

大家可以发现,子类 ElectricCar 、 GasolineCar 并没有定义 constructor 函数。

因为子类没有定义constructor 函数,就意味着继承使用父类(或者祖宗类)的 constructor 函数。

和父类的不同

但是子类往往又不完全和父类一样,否则就没有存在的必要了。

子类和父类不同的地方,可以重新定义。

比如,上面的示例中, 电动车 类型,就比 类型 多出了 hasBattery、hasEngine 着两个属性。

而且 type属性的值 和 父类也不同。

这些都要重新定义。


特别要注意的是:

子类 实例化时,如果构造函数里面行为 和 父类不完全一样,这时候,就需要 重定义 constructor 函数

比如,每个电动车都要有 电池容量 实例属性,可以这样写

class ElectricCar extends Car {
    type = '电动车'
    hasBattery = true
    hasEngine  = false

    constructor(price, owner, batteryCap) {
        this.price = price
        this.owner = owner
        this.batteryCap = batteryCap
    }
    
    showInfo(){
        super
        console.log(`车型 :${this.type} - 车主 : ${this.owner} - 售价 : ${this.price} - 电池容量 ${this.batteryCap} kwh`)
    }
    
}

var myCar1 = new ElectricCar(300000, '白月黑羽', 200)

我们发现往往子类的初始化代码有一部分是 和 父类 相同的。

如果相同的初始化代码特别多,全写一遍,不但耗费时间,而且将来如果里面有要修改的地方,父类和所有子类代码都要修改。

这样可维护性就比较差。


这时,可以直接使用关键字 super 直接调用父类的constructor代码,如下

class Car {    
    type = '汽车'
    hasTire = true

    constructor(price, owner) {
        this.price = price
        this.owner = owner
    }

    showInfo(){
        console.log(`车型 :${this.type} -  车主 : ${this.owner} - 售价 : ${this.price} `)
    }
}

class ElectricCar extends Car {
    static type = '电动车'
    static hasBattery = true
    static hasEngine  = false

    constructor(price, owner, batteryCap) {
        // 调用父类的构造函数
        super(price, owner)
        this.batteryCap = batteryCap
    }
    
}

这里 super() 调用,就是调用父类的 constructor函数


注意 super不仅仅可以代表调用父类的构造函数方法,也可以调用父类的其他方法。

比如

class Car {    
    type = '汽车'
    hasTire = true

    constructor(price, owner) {
        this.price = price
        this.owner = owner
    }

    showInfo(){
        console.log(`车型 :${this.type} -  车主 : ${this.owner} - 售价 : ${this.price} `)
    }
}

class ElectricCar extends Car {
    type = '电动车'
    hasBattery = true
    hasEngine  = false

    constructor(price, owner, batteryCap) {
        // 调用父类的构造函数
        super(price, owner, batteryCap)
        this.batteryCap = batteryCap
    }

    showInfo(){
        super.showInfo()
        console.log(`电池容量 :${this.batteryCap}`)
    }
    
}

var myCar1 = new ElectricCar(300000, '白月黑羽', 200)
myCar1.showInfo()

父类 和 子类, 是相对的概念。

一个子类,同时还可以是另一个类的父类。

比如:上面的车的例子, 我们还可以定义 特斯拉Model 3 作为 电动车 的 子类。

电动车 是 特斯拉Model 3 的父类, 同时又是 车 的 子类。