自己整理了一下关于继承方面的知识,写上自己的一些体会,
通过举栗子的方式帮助大家理解,希望大家能有所收获。
文笔简陋,忘体谅....
一、什么是继承呢?
从字面上来看,继承可以理解为儿子继承了父亲的一些特征,
比如说儿子继承了父亲的交际能力,一些外貌特征,
但是儿子自己本身又具备一些他自己独特的特征,比如说他的交际能力更强于
父亲的交际能力,外貌特征方面跟父亲又有不同的地方。
那么在程序里面继承如何理解呢?
继承是指一个对象直接使用另一对象的属性和方法。
但是对象本身又可以具备自己的属性和方法。
那么如何理解多重继承呢?
一个儿子不仅仅可以继承父亲,他还可以继承母亲,这就是多重继承。
他会具备父亲跟母亲共同的特征。
在程序里面的意思就是说
一个对象可以继承多个对象,可以直接使用多个继承对象的属性和方法。
二、如何实现继承呢?
下面用5种方式实现继承:
- 原型(prototype)实现继承
- 父类实例实现继承
- 利用空对象作为中介实现继承
- call,apply实现多重继承
- es6 extends 实现继承
1.原型(prototype)实现继承
看案例:
/**
* 父类
* @param {[type]} name [description]
* @param {[type]} age [description]
*/
function Father(name,age) {
this.name=name;
this.age=age;
this.father='父亲';
}
/**
* 父类的原型方法
* @return {[type]} [description]
*/
Father.prototype.sayHello=function(){
alert('你好'+this.name);
}
// 实例化父亲这个类
var fat=new Father('父亲',42);
// 调用父类的方法,输出你好,父亲
fat.sayHello();
/**
* 创建子类
*/
function Child(name,age){
this.name=name;
this.age=age;
}
/**
* 通过原型链实现继承 子类的原型等于父亲的原型
* @type {[type]}
*/
Child.prototype=Father.prototype;
//实例化子类
var chi=new Child('小钱',12);
//子类的原型等于子类自己本身
Child.prototype.constructor = Child;
alert(chi.father);//调用父类的father属性,输出undefined
chi.sayHello();//调用父类的sayHello方法,输出你好,小钱,覆盖了父类的name
重点在:
/**
* 通过原型链实现继承 子类的原型等于父类的原型
* @type {[type]}
*/
Child.prototype=Father.prototype;
//实例化子类
var chi=new Child('小钱',12);
//子类的原型等于子自己本身
Child.prototype.constructor = Child;
alert(chi.father);//调用父类的father属性,输出undefined
chi.sayHello();//调用父类的sayHello方法,输出你好,小钱,覆盖了父类的name
子类通过prototype继承了父类,
它相当于完全删除了prototype 对象原先的值,然后赋予一个新值
任何一个prototype对象都有一个constructor属性,指向它的构造函数。
所以目前子类的constructor属性是指向父类的constructor,
这显然会导致继承链的紊乱(明明是用构造函数Child生成的),因此我们必须手动纠正,将Child.prototype对象的constructor值改为Child
代码如下:
//子类的原型等于子类自己本身
Child.prototype.constructor = Child;
这是很重要的一点,编程时务必要遵守
这边还存在一个问题,输出父类的father属性的时候,出现了undefined的问题
alert(chi.father);//调用父类的father属性,输出undefined
这是因为Child.prototype和Father.prototype现在指向了同一个对象,那么任何对Child.prototype的修改,都会反映到Father.prototype,也就是说目前Father的constructor也变成了Child,那么自然不存在father属性了,所以出现了undefined的问题,
那么我们下面看一下构造函数实现继承的方式。
2. 父类实例实现继承
看案例:
/**
* 父类
* @param {[type]} name [description]
* @param {[type]} age [description]
*/
function Father(name,age) {
this.name=name;
this.age=age;
this.father='父亲';
}
/**
* 父类的原型方法
* @return {[type]} [description]
*/
Father.prototype.sayHello=function(){
alert('你好'+this.name);
}
// 实例化父亲这个类
var fat=new Father('父亲',42);
// 调用父类的方法,输出你好,父亲
fat.sayHello();
/**
* 创建子类
*/
function Child(name,age){
this.name=name;
this.age=age;
}
/**
* 通过原型链实现继承 子类的原型等于父类的实例
* @type {[type]}
*/
Child.prototype=new Father();
//实例化子类
var chi=new Child('小钱',12);
//子类的原型等于子类自己本身
Child.prototype.constructor = Child;
alert(chi.father);//调用父类的father属性,输出父亲
chi.sayHello();//调用父类的sayHello方法,输出你好,小钱,覆盖了父类的name
与第一种方式不同的地方:
/**
* 通过原型链实现继承 子类的原型等于父类的实例
* @type {[type]}
*/
Child.prototype=new Father();
//实例化子类
var chi=new Child('小钱',12);
//子类的原型等于子类自己本身
Child.prototype.constructor = Child;
alert(chi.father);//调用父类的father属性,输出父亲
chi.sayHello();//调用父亲的sayHello方法,输出你好,小钱,覆盖了父类的name
这样就可以解决上面第一种直接用原型继承的问题,
通过父类的实例去实现继承,那么父类的constructor就不会发生变化,就不存在Child的prototype与Father的prototype同时一个指向的问题了。
3.利用空对象作为中介实现继承
看案例:
/**
* [继承方法]
* @param {[type]} Child [子类]
* @param {[type]} Parent [父类]
* @return {[type]} [description]
*/
function extend(Child, Parent) {
//创建一个空的对象
var F = function() {};
//空对象通过父类实例实现继承
F.prototype = new Parent();
//子类的原型等于空对象的实例
Child.prototype = new F();
//改变子类的构造函数为自身
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
创建一个空对象最为中介实现继承,因为F是空对象,所以几乎不占内存。
调用方法如下:
//调用继承方法
extend(Child, Father);
//实例化子类
var chi=new Child('小钱',12);
alert(chi.father);//调用父类的father属性,输出父亲
chi.sayHello();//调用父类的sayHello方法,输出你好,小钱,覆盖了父类的name
4.call,apply实现多重继承
看案例:
/**
* 父类
* @param {[type]} name [description]
* @param {[type]} age [description]
*/
function Father(name,age) {
this.name=name;
this.age=age;
this.father='父亲';
}
/**
* 父类的原型方法
* @return {[type]} [description]
*/
Father.prototype.sayHello=function(){
alert('你好'+this.name);
}
/**
* 母亲类
* @param {[type]} name [description]
* @param {[type]} age [description]
*/
function Mother(name,age) {
this.name=name;
this.age=age;
this.mother='母亲';
}
/**
* 母亲类的原型方法
* @return {[type]} [description]
*/
Mother.prototype.sayGoodbye=function(){
alert('再见'+this.name);
}
// 实例化父亲这个类
var fat=new Father('父亲',42);
// 实例化母亲这个类
var mot=new Mother('母亲',41);
// 调用父类的方法,输出你好,父亲
fat.sayHello();
// 调用母亲类的方法,输出再见,母亲
mot.sayGoodbye();
/**
* 创建子类
*/
function Child(name,age){
this.name=name;
this.age=age;
/**
* 使用apply实现继承,将父对象的构造函数绑定在子对象上
* 绑定父类,绑定母类,实现多重继承
*/
Father.apply(this,arguments);
Mother.apply(this,arguments);
}
//实例化子类
var chi=new Child('小钱',12);
alert(chi.father);//调用父类的father属性,输出父亲
alert(chi.mother);//调用母类的mother属性,输出母亲
chi.sayHello();//调用父类的sayHello方法,输出你好,小钱,覆盖了父类的name
chi.sayGoodbye();//调用母亲类的sayHello方法,输出再见,小钱,覆盖了母亲的name
看重点:
/**
* 创建子类
*/
function Child(name,age){
this.name=name;
this.age=age;
/**
* 使用apply实现继承,将父对象的构造函数绑定在子对象上
* 绑定父类,绑定母类,实现多重继承
*/
Father.apply(this,arguments);
Mother.apply(this,arguments);
}
在子类构造函数中,使用apply或call实现继承
那么子类就会同时具备2个父类的属性和方法
注:call 跟 apply 的区别,本文暂不做介绍
5.es6 extends实现继承
看案例:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
Class之间可以通过extends
关键字实现继承,这比ES5的通过修改原型链实现继承方便很多。
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
如果子类没有定义constructor方法,这个方法会被默认添加。
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
具体实现方式这边不做太多详细解释,想了解更多,可以去看看阮老师的es6入门
总结这4中方法的优缺点:
第一种直接继承prototype,即child.prototype = parent.prototype;
优点:相比第二种效率更高,比较省内存;
缺点:因为重新赋值了子类,如果子类的prototype对象上有属性或方法时,将被清除;
且子对象的prototype对象修改后父对象的prototype也会被修改;第二种:父类实例继承模式,即把子对象的prototype对象指向父类的一个实例;
优点: 解决了第一种父类原型也会被修改的问题
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;
注意:当改了prototype对象的constructor时,记得改回来,否则将造成继承链紊乱;
**一定要遵循这样的规则,即如果替换了prototype对象,那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
**
o.prototype = {};
o.prototype.constructor = o;
第三种: 利用空对象作为中介,第一种第二种的结合体
优点:因为空对象不占内存,相比第二种效率更高,比较省内存;
缺点:如果子对象的prototype对象上有属性或方法时,将被清除;第四种: 使用call(apply)把父对象的this指向改为子对象
优点: 写法比较简便,很方便的可以实现多重继承第五种: 使用es6 extends实现继承
优点: 写法比较方法,结构比较清晰
缺点: 浏览器兼容性还不是很好,需要babel编译成目前可以识别的es5
以上都是本人的一些总结,如果有体会不到位,错误的地方,烦请大家指出,谢谢