前言
ES5的继承方式有多种。很多的继承方式也会存在一些问题,下面我将对ES5中常见的几种继承方式进行一些阐述。
本文总结的继承有:
- 冒充继承(构造函数继承)
- 原型式继承
- 组合式继承
- 寄生式继承
一、 冒充继承(构造函数继承)
原理:调用父类构造函数,并改变其中的this, 通过bind、call、apply来改变this。
这种继承方式有局限性,“父类”原型上的属性方法无法继承。下面从代码角度看一下是怎么实现的。
// 构造函数继承(对象冒充继承)
function Cat(n,c){ // 猫 类
this.name = n;
this.color = c;
this.trait = function (){
console.log('卖萌');
}
}
Cat.prototype.skill = function (){ // 原型上的属性方法
console.log('抓老鼠');
}
// 需求:狗要卖萌,狗要多管闲事-抓老鼠
function Dog(n,c,f){ // 狗 类
this.food = f;
Cat.call(this,n,c); // 狗冒充猫,访问猫的属性方法
}
var dog1 = new Dog('二哈','yellow','shi');// 实例对象
console.log(dog1.name); // 二哈
dog1.trait(); // 卖萌
dog1.skill(); // “父类”原型上的属性方法无法继承,所以会报错 dog1.skill is not a function
二、原型式继承
原理:将原型对象链接到另一个对象实现继承(改变原型的指向)。
这种继承方式有局限性,实例化对象的时候不能给“父类”传参,并且其constructor指向的是继承对象的constructor。下面从代码角度看一下是怎么实现的。
// demo2 原型链继承
// 原理:将原型对象链接到另一个对象实现继承(改变原型的指向)
function Cat(n,c){ // 猫 类
this.name = n;
this.color = c;
this.trait = function (){
console.log('卖萌~');
}
}
Cat.prototype.skill = function (){// 原型上的属性方法
console.log('抓老鼠');
}
function Dog(n,c,f){ // 狗 类
this.food = f;
}
Dog.prototype = new Cat(); // 把狗的原型指向猫的实例对象
var dog1 = new Dog('二哈','yellow','shi');
console.log(dog1.name); // undefined
console.log(dog1.food); // shi
dog1.trait(); // 卖萌~
dog1.skill(); // 抓老鼠
console.log(dog1.constructor); // Cat
三、组合式继承
这种方式是将上边的冒充继承和原型链继承结合起来。 组合继承解决原型链继承不能传参的问题,以及冒充继承不能继承原型对象的属性问题,但是组合继承还是存在问题:1、不能传递参数到原型对象,2、执行了两次构造函数
// 组合式继承
function Ball(_a){
//冒充继承
Box.call(this,_a);
}
//原型式继承
Ball.prototype=new Box();
Ball.prototype.constructor=Ball;
var b=new Ball(10);
console.log(b);
//两种方式结合可以实现相对比较完美的“继承”
//别忘了指正构造器(类型)
四、寄生式继承
下面代码实现了寄生式继承,此方式解决了组合式继承存在的问题,是一种完美的继承方式。
//寄生式继承
function extend(subClass, supClass) {
// 创建一个中间替代类,防止多次执行父类(超类)的构造函数
function F() { }
// 将父类的原型赋值给这个中间替代类
F.prototype = supClass.prototype;
// 将原子类的原型保存
var proto = subClass.prototype;
// 将子类的原型设置为中间替代类的实例对象
subClass.prototype = new F();
// 将原子类的原型复制到子类原型上,合并超类原型和子类原型的属性方法,只能复制可枚举属性
//Object.assign(subClass.prototype,proto);
var names = Object.getOwnPropertyNames(proto);
for (var i = 0; i < names.length; i++) {
var desc = Object.getOwnPropertyDescriptor(proto, names[i]);
Object.defineProperty(subClass.prototype, names[i], desc);
}
// 设置子类的构造函数是自身的构造函数,以防止因为设置原型而覆盖构造函数(可以不用写因为已经复制了)
subClass.prototype.constructor = subClass;
// 给子类的原型中添加一个属性,可以快捷的调用到父类的原型方法
subClass.prototype.superClass = supClass.prototype;
// 如果父类的原型构造函数指向的不是父类构造函数,重新指向
if (supClass.prototype.constructor !== supClass) {
supClass.prototype.constructor = supClass;
}
}
//创建Box构造函数
function Box(_a) {
console.log("aaa");
//实例化对象中有a这个属性
this.a = _a;
//执行原型对象下的play方法
this.play();
}
//设置原型对象的属性a
Box.prototype.a = 10;
//设置原型对象的选访问器属性(为了找到问题才加的)(为啥出现在了实例化对象中)
Object.defineProperty(Box.prototype, "num", {
set: function (value) {
this._num = value;
},
get: function () {
if (!this._num) this._num = 0;
return this._num;
}
})
Box.prototype.play = function () {
console.log("play");
}
Box.b = 20;
function Ball(_a) {
this.superClass.constructor.call(this, _a);
}
Ball.prototype.play = function () {
this.superClass.play.call(this);//执行超类的play方法
console.log("end");
}
Object.defineProperty(Ball.prototype, "d", {
value: 20
})
extend(Ball, Box);
var b = new Ball(10);
console.log(b);