第二章 面向对象编程之继承,web开发好学吗

35 阅读7分钟

}

// 声明子类

function SubClass(id){

SuperClass.call(this,id);

}

在这里插入图片描述

注意,SuperClass.call(this,id) 这条语句是构造函数式继承的精华,由于call这个方法可以改变函数的作用环境,因此在子类中,对SuperClass调用这个方法就是将子类的变量在父类中执行一遍,由于父类是给this绑定属性的,因此子类自然也就继承了父类的共有属性。但是这种类型的继承并没有涉及原型prototype,所以父类的原型方法自然不会被继承。

组合继承——类式继承和构造函数式继承合体


// 组合式继承

// 声明父类

function SuperClass(name){

this.name = name

this.books = ['html','css','js']

}

// 父类原型共有方法

SuperClass.prototype.getName = function(){

console.log(this.name)

}

// 声明子类

function SubClass(name,time){

// 构造函数式继承父类name属性

SuperClass.call(this,name)

// 子类中新增共有属性

this.time = time;

}

// 类式继承

SubClass.prototype = new SuperClass()

// 子类原型方法

SubClass.prototype.getTime = function(){

console.log(this.time)

}

var instance1 = new SubClass('js book',2021)

instance1.books.push('node.js')

console.log(instance1.books)

instance1.getName()

instance1.getTime()

var instance2 = new SubClass('css book',2022)

instance2.books.push('h5')

console.log(instance2.books)

instance2.getName()

instance2.getTime()

在这里插入图片描述

分析一下instance的构造:

在这里插入图片描述

组合继承模式将类式继承和构造函数式继承相结合,可以看到构造函数式继承将父类的属性复制到子类(每次执行构造函数式继承,相当于重新new一个父类实例,所以不会有相互覆盖的可能),但是不涉及到protype,所以不会继承父类的原型方法。所以接下来马上使用类式继承,将父类的实例赋值给子类的prototype,因为子类原型prototype上继承父类的共有属性(比如books)和之前子类执行构造函数时生成的books属性同名,所以instance.books不会取原型链上的books属性,但是能够取到原型链上的父类原型pototype上的方法。

综上,组合继承完美结合了类式继承和构造函数式继承的优点,并且消除了他们的缺点。但是这种继承也不是完全没有缺点,就是执行了2遍的父类构造函数。

那有没有办法只执行1遍父类的构造函数,以减少消耗呢?

方法肯定是有的,那就是使用终结继承大法——寄生组合式继承!

终极继承大法——寄生组合式继承


原型式继承——封装类式继承

在说明终极继承大法之前,我们先学习一下它的组成之一,原型式继承。

function inheritObject(o){

// 声明一个过渡函数对象

function F(){}

// 过渡对象的原型继承父对象

F.prototype = o

// 返回过渡对象的一个实例,该实例的原型继承了父对象

return new F()

}

可以看出原型式继承和类式继承十分相似,实际上它就是类式继承的一个封装。只不过这里的子类是以过渡对象的形式出现的。

寄生式继承——原型式继承的二次封装

// 声明基对象

var book = {

name:'js book',

alikeBook:['css book','html book']

}

// 原型式继承

function inheritObject(o){

// 声明一个过渡函数对象

function F(){}

// 过渡对象的原型继承父对象

F.prototype = o

// 返回过渡对象的一个实例,该实例的原型继承了父对象

return new F()

}

// 寄生式继承

function createBook(obj){

// 通过原型式继承创建新对象

var o = new inheritObject(obj)

// 扩展新对象

o.getName = function(){

console.log(this.name)

}

return o

}

var newBook = new createBook(book)

在这里插入图片描述

寄生式继承其实就是对实例化后的原型继承对象进行扩展。

寄生组合式继承——终极

// 原型式继承

function inheritObject(o){

// 声明一个过渡函数对象

function F(){}

// 过渡对象的原型继承父对象

F.prototype = o

// 返回过渡对象的一个实例,该实例的原型继承了父对象

return new F()

}

// 寄生组合式继承

function inheritPrototype(subClass,superClass){

// 复制一份父类的原型副本保存在变量中

var p = inheritObject(superClass.prototype)

// 修正因为重写子类原型导致子类的constructor属性被修改

p.constructor = subClass

subClass.prototype = p

}

// 定义父类

function SuperClass(name){

this.name = name

this.colors = ['#000000','#ffffff','#000fff']

}

// 定义父类原型方法

SuperClass.prototype.getName = function(){

console.log(this.name)

}

// 定义子类

function SubClass(name,time){

// 构造函数式继承

SuperClass.call(this,name)

// 子类新增属性

this.time = time

}

// 寄生式继承父类原型

inheritPrototype(SubClass,SuperClass)

// 子类新增原型方法

SubClass.prototype.getTime = function(){

console.log(this.time)

}

// 创建2个测试方法

var instance1 = new SubClass('js book',2021)

var instance2 = new SubClass('css book',2022)

instance1.colors.push('red')

console.log(instance1.colors)

console.log(instance2.colors)

instance2.getName()

instance2.getTime()

在这里插入图片描述

分析下instance1的结构:

在这里插入图片描述

寄生组合式继承替换掉类式继承,与构造函数式继承的结合堪称完美。首先构造函数式继承复制了父类的共有属性,然后寄生组合式继承只是将父类的原型prototype复制到了子类的prototype,并且还修正了子类constructor属性。

多继承


JavaScript不是只有一条原型链吗?怎么实现多继承呢?我们来看下下面的一段代码:

在这里插入图片描述

通过extend方法,我们成功将source中的属性成功复制到了target对象中,但是仅仅这样还不够,会出现几个问题:

1)如果target中有相同的属性,就会被source所覆盖

2)如果source中的某个属性是引用类型,就会导致target复制的该属性指向同一地址

3)extend方法没有涉及到prototype

解决这几个问题后,我们再考虑多继承的问题,不过是将source变成多个而已。

在这里插入图片描述

到这里,发现自己手写了一个深拷贝,解决了前2个问题,那prototype的问题呢?考虑到多继承和单继承不同,在此不做讨论,如果prototype也需要继承的话,可以参考extend方法。

接下来,我们需要扩展extend方法,将复制一个对象转为复制多个对象的属性。