聊聊继承的继承方式

204 阅读6分钟
这阶段给大家来讲讲javaScript里面的继承,虽然在ES6中有了继承,使用extends关键字就能实现,而这篇要讲的,而是ES6之前的几种实现继承的方式;
讲继承,大家肯定需要先了解的是对象,和原型对象以及原型链,这个知识点我在上一个帖子中已经有过描述,大家可以去看上篇帖子;
那么首先,大家需要知道,
什么是继承?

继承概念

我们可以这样去理解,子承父业,父亲拥有的东西,孩子可以去享用;用于把对象里面一个公用的属性和方法提取出来,这样,孩子就不用去重复去写这些属性和方法,直接可以用父亲的;

了解到了继承的概念,那么我们来看看在javaScript里面如何来实现继承

继承实现方式



1、 原型继承
也就是把系统给我们默认定义的原型对象赋值成我们自己的实例对象,我们来看一段代码

/**
父亲构造函数
*/
function Parent() {
this.name = '父亲';
}
/**
孩子构造函数
*/
function Child() {
}
//把原型对象赋值给 父亲的实例化对象
Child.prototype = new Parent();
//原型重新赋值了,需要把constructor指向对应的构造,不然匹配不出对应的类型
Child.prototype.constructor =Parent;
var child = new Child();
console.log(child.name);//
父亲

这里,把
Parent
的实例化对象赋值给
Child
的原型,那么
Child
的原型就指向了这个实例化的对象,在我们调用
child.name
的时候,先会从自身找有没有这个属性,如果没有,找自己的原型对象,而此时的原型对象已经赋值给了
Parent
的实例,所以通过这点就找到了
Parent
name
属性,最后把值
'父亲'
输出在控制台,可能这样描述还是有点晕晕的,没关系,下面给大家画一个图来讲解下





上面的图是我们声明两个构造函数以及实例化对象时候的原型关系图,此时两者是没有关系的,而真正让Child和
Parent
发生关系的代码是
Child.prototype = new Parent();
这一句,这一句也是把我们
Child
孩子的原型对方赋值成了
Parent
的实例对象,当代码执行完后,他们的关系就发生了变化





这种方式优缺点:

优点:
  • 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  • 父类新增原型方法/原型属性,子类都能访问到
  • 简单,易于实现

缺点:
  • 不能传递参数
  • 来自原型对象的所有属性被所有实例共享


2、 借用构造函数



借用构造函数,顾名思义,借用别人的构造函数,但是我们一般在声明构造函数的时候,会用this这个关键字来给里面的属性赋值,所以在借用构造函数的时候,需要去更改this的指向问题,先来看一段代码

/**
父亲构造函数
*/
function Parent(name, age) {
this.name = name;
this.age = age;
}
/**
孩子构造函数
*/
function Child(name, age) {
//利用call方法,和bind方法差不多意思,但是利用call方法能让函数直接调用,第一个参数是指向,后面的参数代表传递过去的实参
Parent.call(this, name, age);
}
var child = new Child('孩子', 18);
console.log(child.name,child.age);


利用call方法,call方法会直接调用该函数,并且会把该函数里面的this指向更改,更改成call方法里面的第一个参数,Parent.call(this, name, age);里面的this指向的当前实例化Child的对象,那么在Parent里面的this也指向的它。所以在输出的时候能够取到name属性和age属性;


这种方式优缺点:
优点:
  • 解决了方法1中的问题,不会共享原型对象里面属性
  • 可以向父类传递参数

缺点:
  • 实例并不是父类的实例,只是子类的实例
  • 能继承父类的实例属性和方法,不能继承原型属性/方法


3、 组合继承



这种继承方式是结合了上面两种,取之精华,来实现既能进行参数传递,又可以调用原型里面定义的方法

/**
父亲构造函数
*/
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayHi = function() {
console.log(this.name + "hi");
}
/**
孩子构造函数
*/
function Child(name, age) {
Parent.call(this, name, age);
}
//把原型赋值给Parent的实例化对象
Child.prototype = new Parent();
var child = new Child('孩子', 18);
console.log(child.name, child.age);
child.sayHi();//孩子
Hi


来看关系图:




我们给Parent的原型对象上设置了一个sayHi(),然后把Parent的实例化对象赋值给了Child的原型对象,这样通过原型链的搜索规则,通过new Child();得到Child的实例对象,可以一步一步找到Parent的原型对象,从而拿到sayHi();


特点:
  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 既是子类的实例,也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用

缺陷:
  • 调用了两次父类构造函数

4、寄生组合继承



构造函数的目的是为了复制属性,Person.call( this, name, age);肯定是不能少的,Child.prototype = new Parent();的目的是为了获取到父类原型对象(prototype)上的方法,基于这个目的,有没有别的方法可以做到,在不需要实例化父类构造函数的情况下,也能得到父类原型对象上的方法呢? 当然可以,我们可以采用寄生式继承来得到父类原型对象上的方法

function Parent(name, age) {
this.name = name;


this.age = age;


}


Parent.prototype.sayHi = function() {


console.log(this.name + " sayHi");


}


function Child(name, age) {


Parent.call(this, name, age);


}


(function() {


var Super = function() {};


//讲父类的原型对象赋值给这个超类


Super.prototype = Parent.prototype;


//将实例作为子类的原型


Child.prototype = new Super();


})();



var child = new Child('zs', 19);


console.log(child.name);


child.sayHi();



其实,说白了寄生组合式继承就是一个借用构造函数 + 相当于浅拷贝父类的原型对象的方式,下面图来描述他们的原型关系




原来Super这个构造函数跟Parent构造函数是没有关系的,但是在我们代码里有一个重要的自调用函数

(function() {


var Super = function() {};


//讲父类的原型对象赋值给这个超类


Super.prototype = Parent.prototype;


//将实例作为子类的原型


Child.prototype = new Super()


;})();



这几行代码让两者产生了关联,把Parent的原型对象,赋值给了Super,然后把Super的实例对象赋值给了Child的原型对象,如图:



上图,通过Super.prototype = Parent.prototype;这行代码赋值,让Parent的原型对象跟Super原型对象的指向了同一个内存地址;


以上内容就是对javaScript的继承的方式进行了一些整理,希望对大家会有帮助!