目录
- 前言
- 一、原型链继承
- 原型链继承的概念
- 原型链继承的不足
- 二、借用构造函数继承
- 借用构造函数继承的概念
- 利用构造函数继承的不足
- 三、组合继承(原型链继承+构造函数继承)
- 组合继承的概念
- 组合继承的缺陷
- 四、寄生组合性继承
- 寄生组合性继承的概念
- 寄生组合性继承的优点
- 多重继承
- 多重继承的概念
- 总结
前言
在JS中既然没有类,那继承也是没有的,但面对家财万贯的家产时,我们又岂能无动于衷呢?于是我们想到了用模拟的方式,实现继承。
一、原型链继承
原型链继承的概念
在前一篇文章(javaScript(ES5)面向对象——原型模式实现原理),我们已经分析过了,在JS中每一个对象都存在原型对象,且可以通过原型对象共享属性和方法。原型链继承
就是通过改变子类的原型对象为父类的原型对象
,从而实现继承。
见图所示:
实现代码:
function Farther(){
this.name = ['hello'];
this.money = 999999;
}
Farther.prototype.say = function(){
console.log(this.name);
}
function Son(){
}
// 原型链继承
Son.prototype = new Farther();
Son.prototype.constructor = Son;
var xdl = new Son();
console.log(xdl.money); // 999999
xdl.say(); // ["hello"]
原型链继承的不足
原型链继承一共有以下两个问题:
- 原型链继承中,子类的实例对象会共享父类的
实例对象属性
,当父类的实例对象属性是引用数据类型
时,一旦子类中的实例对象对其修改,则子类所有的实例对象所继承的相应属性值都会发现改变
。
var xdl = new Son('xdl');
var xdl2 = new Son('xdl2');
xdl.name.push('world!');
console.log(xdl.name); // ["hello", "world!"]
console.log(xdl2.name); // ["hello", "world!"]
- 当创建子类的实例对象时,
无法传参
给父类,也就无法使用父类的构造函数
。
二、借用构造函数继承
借用构造函数继承的概念
为了解决使用原型链继承
无法在创建子类实例对象时,无法传参给父类
的缺陷,因此我们使用call(apply、bind)
的方式来调用父类的构造函数,从而实现子类向父类传参的效果。
实现代码:
function Father(name){
this.name = name;
this.sleep = function(){
console.log('正在睡觉');
}
}
Father.prototype.money = 999999;
Father.prototype.say = function(){
console.log(this.name);
}
function Son(name){
Father.call(this, name);
}
var xdl = new Son('xdl');
console.log(xdl.name); // xdl
console.log(xdl.money); // undefined
console.log(xdl);
打印结果:
利用构造函数继承的不足
我们在打印结果中可以看出,子类只继承了父类的实例对象属性和方法
,但父类的原型对象并没有被继承下来
。也就是说,父亲的家产没有被继承下来啊,那怎么行!
三、组合继承(原型链继承+构造函数继承)
组合继承的概念
原型链继承和构造函数继承,都各有各的优点与缺点,都说鱼和熊掌不可兼得,但为了继承到完整的家产,我们必须要拼命想办法继承。
实现代码:
function Father(name){
this.name = name;
console.log('我被调用了');
}
Father.prototype.money = 999999;
function Son(name){
Father.call(this, name);
}
Son.prototype = new Father(); // 父类的创建的对象当作子类的原型对象
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);
console.log(xdl.money);
运行结果:
组合继承的缺陷
分析结果我们可以得出,父类的构造函数会被调用两次
:一次是实例化子类属性和方法
,第二次是继承父类的共享属性和方法
。
四、寄生组合性继承
寄生组合性继承的概念
在组合继承中无非就是调用父类构造函数的次数过多,那我们就想办法减少调用的次数。那我们应该从哪里入手?
我们来分析一下:一共有两个地方调用父类的构造函数,第一个地方:Father.call(this, name);
,这个地方是通过call
的方式,调用父类构造函数,从而继承父类实例对象的属性和方法;第二个地方:Son.prototype = new Father();
,这个地方是将父类的实例对象当作子类的原型对象
,继承父类的共享属性和方法。第一个地方无法优化,但第二个地方,不就是要得到父类的原型对象吗?那我们直接把父类的原型对象取出来
,再用Object.create()
以父类的原型对象为基础创建一个对象,最后作为子类的原型对象。
实现代码:
function Father(name){
this.name = name;
}
Father.prototype.money = 999999;
function Son(name){
Father.call(this, name);
}
Son.prototype = Object.create(Father.prototype); // 优化的地方
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);
console.log(xdl.money);
运行结果:
寄生组合性继承的优点
在寄生组合性继承
中,创建子类的实例对象时,可以向父类传参
,继承父类实例对象的属性和方法
,同时还可以继承父类的原型对象
,得到共享的属性和方法
,并且不会像组合性继承那样调用两次父类构造函数,成功继承父亲全部的家产!
多重继承
多重继承的概念
在前面,我们成功继承了父亲的全部家产,但是母亲的家产呢?母亲给予我们更多的是暖暖的母爱,所以我们必须要得到这无价的爱。这就涉及到了多重继承的问题,在JS中通过混入技术
(把另外一个对象拷贝到当前原型链上),把父类的原型对象拷贝到当前的原型链上
,从而实现多重继承。
实现代码:
function Animal(name){
this.name = name;
}
function Father(){
this.name = '父亲';
}
Father.prototype.money = 999999;
function Mother(){
this.name = '母亲'
}
Mother.prototype.love = function(){
console.log('母亲的关爱');
}
function Son(name){
Animal.call(this, name);
}
Son.prototype = Object.create(Father.prototype);
Object.assign(Son.prototype, Mother.prototype);
Son.prototype.constructor = Son;
var xdl = new Son('xdl');
console.log(xdl);
console.log(xdl.money);
xdl.love();
运行结果:
总结
谁说鱼和熊掌不能兼得?成功通过足智多谋,继承到了父母的家产,父母再也不用担心家产没有人继承了!