上一文已经写了es5,es6等类的定义,所以本章主要写es5和es6的继承,由于es6的继承和ts的继承如出一辙,只是加了类型定义而已,所以ts的继承稍微写下,不会太详细。
es5继承
原型链继承
所谓原型链继承,就是将一个实例对象挂载到另一个原型上。即继承对象的prototype = new 实例化的对象
// 声明一个Parent构造函数 function Parent (name) { this.name = name; this.prientName = function () { console.log(`我的名字叫${this.name}`) } } Parent.prototype.print = function () { console.lgo('hello,小胖纸') } // 在声明一个Child构造函数 function Child (age) { this.age = age; this.prientAge = function () { console.log(this.age) } } // 此时两个构造函数还是没有任何交集的,我们通过原型链的特性来进行继承 // 原型链特性,在prototype找不到会顺着__proto__一直向父级找,直到Function为止 Child.prototype = new Parent(); new Child('小胖纸').prientName() // 我的名字叫undefined new Child('小胖纸').print () // hello,小胖纸
这样我们就实现了Child继承Parent的属性和方法,但是调用父类的方法我们发现,我们无法传值到父类里面,也就是父类的构造函数接收不到我们的传值,这也是原型链继承的一个弊端。
构造函数继承
想实现构造函数继承我们不得不依赖call或者apply,通过改变this指向
// 声明一个Parent构造函数 function Parent (name) { this.name = name; this.prientName = function () { console.log(`我的名字叫${this.name}`) } } Parent.prototype.print = function () { console.lgo('hello,小胖纸') } // 在声明一个Child构造函数 function Child (name, age) { // 借用call方法,我们更改Parent内部this的指向,使其指向Child的实例化化对象,同时也可将值进行传递 Parent.call(this, name) this.age = age; this.prientAge = function () { console.log(this.age) } } new Child('小胖纸').prientName() // 我的名字叫小胖纸 new Child().print () // error print is not a function
这样我们就能实现继承并且解决不能传值的问题,但是你也发现了,我们这样更改是这样继承不了来自原型的公共属性和方法,而只能在构造函数内部去一一创建,性能不佳。
组合继承
顾名思义,组合继承就是既有原型链继承又有构造函数继承,这样既能解决不能传值的问题,也解决了不能共有的属性和方法
// 声明一个Parent构造函数 function Parent (name) { this.name = name; this.prientName = function () { console.log(`我的名字叫${this.name}`) } } Parent.prototype.print = function () { console.lgo('hello,小胖纸') } // 在声明一个Child构造函数 function Child (name, age) { // 借用call方法,我们更改Parent内部this的指向,使其指向Child的实例化化对象,同时也可将值进行传递 Parent.call(this, name) this.age = age; this.prientAge = function () { console.log(this.age) } } Child.prototype = new Parent(); new Child('小胖纸').prientName() // 我的名字叫小胖纸 new Child().print () // hello,小胖纸
但是这样也有一个小问题,那就是构造函数已经实现了继承父类的属性和方法,只是没法继承公有属性和方法而已,而原型链继承呢?可以继承公有属性和方法,只是不能传值而已,它一样也能继承父类内部的属性和方法,是不是有一小部分重叠?所以嘞,我们稍微改造下Child.prototype = Parent.prototype把子类中的原型直接指向父类的原型就可以啦,这样,构造函数专门来继承父类内部的属性和方法以及传值,而原型链则管着公有属性和方法,各司其职。
es6继承
es6的继承主要是extends关键字,这比es5 的实现继承,要清晰和方便很多。
// 定义一个父类 class Parent { constructor (name) { this.name = name; } prientName () { console.log(this.name) } } // 定义子类继承父类 class Child extends Parent { constructor (name, age) { super(name); this.age = age; } }
这样我们就实现了继承,但是如何传值到父类中呢?这个时候我们还有另外一个关键字super,super什么意思呢?在子类的构造函数中使用就是调用父类的构造函数,当然也可以进行传参。在方法中使用super,可以直接调用父类的方法
// 定义一个父类 class Parent { constructor (name) { this.name = name; } prientName (age) { console.log(`我是个${age}岁的${this.name}`) } } // 定义子类继承父类 class Child extends Parent { constructor (name, age) { super(name); this.age = age; } printAge () { super.prientName (this.age) } } let p1 = new Child('小胖纸', 22); p1.printAge () // 我是个22岁的小胖纸
上面的代码中constructor 和printAge ,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。你可以把super理解成父类的实例化对象。当然这是在普通方法中,在类的静态方法中,super则变成了指代父类对象而不是类的实例的对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。看列子
// 定义一个父类 class Parent { constructor (name) { this.name = name; } } // 定义子类继承父类 class Child extends Parent { constructor (age) { this.age = age; } } // 因为没有super调用父类中的constructor构造函数,所以实例化对象时报错 new Child(22) // error // 定义一个父类 class Parent { constructor (name) { this.name = name; } } // 定义子类继承父类 class Child extends Parent { constructor (name, age) { // this只能在super后面使用 this.age = age; // error super(name) } }
但是他允许你这样
// 定义一个父类 class Parent { constructor (name) { this.name = name; } } // 定义子类继承父类 class Child extends Parent { }
当子类没有constructor时,会默认添加constructor,并调用super方法,但是你要是写上constructor时,那么就必须调用super。
当然如果父类中有static静态属性和方法,也一样会被继承到子类中
ts继承
只是加上了类型而已,如果不知道,请看上一篇文章,ts类的定义
// 定义一个父类 class Parent { // 必须声明 name: string; constructor (name: string) { this.name = name; } prientName (): void { console.log(this.name) } } // 定义子类继承父类 class Child extends Parent { // 必须声明 age: number; // age? 表示存在不确定 constructor (name: string, age?: number) { super(name); this.age = age; } } // 声明p 是Parent类型 let p: Parent = new Child('小胖纸'); // 声明p1 是Child类型 let p1: Child= new Child('大胖纸');