继承是什么?
继承
(inheritance)是面向对象
软件技术当中的一个概念。继承
可以使得子类
具有父类
别的各种属性
和方法
,而不需要再次编写相同的代码。如果一个类别B"继承自"另一个类别A,就把这个B称为"A的子类",而把A称为"B的父类别"也可以称"A是B的超类"。
继承的多种方式
故事背景:作者有两只喵,大儿金吉拉(小白),小儿布偶猫(小布),默认他们的爸爸都爱吃饭和睡觉,毕竟他们也爱吃饭和睡觉,大儿聒噪(哼小曲),小儿优雅乖巧,论二胎家庭是无法做到一碗水端平的吧
1. 原型链继承(Prototype Inheritance):
- 实现方式: 子类构造函数的原型对象指向父类的实例。
- 优点: 简单易懂,易于实现。
- 缺点:
- 引用类型的属性会被所有实例共享,存在属性共享的问题。
- 不能向父类传递参数, 无法通过子类向父类传参。
js
function Parent() {
this.name = '爸爸'
this.hobbies = ['吃饭','睡觉'];
}
Parent.prototype.getHobbies = function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};
function Child(name) {
this.name = name;
this.hobbies = ['哼小曲'];
}
Child.prototype = new Parent();
var parent = new Parent();
parent.getHobbies(); //爸爸的爱好是吃饭和睡觉
var child = new Child('小白');
child.getHobbies(); //小白的爱好是哼小曲
问题1:引⽤类型的属性被所有实例共享,举个例⼦:
js
function Parent() {
this.name = '爸爸'
this.hobbies = ['吃饭','睡觉'];
}
Parent.prototype.getHobbies = function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};
function Child(name) {
this.name = name;
}
// 子类的原型指向父类的实例
Child.prototype = new Parent();
var child1 = new Child('小白');
var child2 = new Child('小布');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉
child2.getHobbies(); // 小布的爱好是吃饭和睡觉
// 修改小白的爱好,小布的爱好也跟着变了
child1.hobbies.push('哼小曲');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲
child2.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲
问题2:不能向父类传递参数,举个例⼦:
js
function Parent(name) {
this.name = name || '爸爸';
}
Parent.prototype.sayHello = function() {
console.log('你好, 我是' + this.name);
};
function Child() {
// 子类构造函数中没有传递参数给父类
}
Child.prototype = new Parent();
var child1 = new Child('小布');
child1.sayHello(); // 你好, 我是爸爸
子类虽然传了名字,依然输出爸爸的名字,向父类传递参数失败
2. 构造函数继承(Constructor Inheritance):
- 实现方式: 在子类构造函数中调用父类构造函数,并使用
call
或apply
方法绑定this
。-
优点:
- 避免了属性共享问题。
- 可以向父类传递参数。
-
缺点:
- 不能继承父类原型上的方法。
- 每次创建子类实例都会创建一份父类方法的副本,存在内存浪费。
-
js
function Parent(name) {
this.name = name || '爸爸';
this.hobbies = ['吃饭','睡觉'];
}
function Child(name) {
Parent.call(this, name);
}
var parent = new Parent();
console.log(parent.name + '的爱好是' + parent.hobbies.join('和')); // 爸爸的爱好是吃饭和睡觉
var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
console.log(child1.name + '的爱好是' + child1.hobbies.join('和')); // 小白的爱好是吃饭和睡觉和哼小曲
var child2 = new Child('小布');
console.log(child2.name + '的爱好是' + child2.hobbies.join('和')); // 小布的爱好是吃饭和睡觉
问题1: 不能继承父类原型上的方法
js
function Parent(name) {
this.name = name || '爸爸';
this.hobbies = ['吃饭','睡觉'];
}
Parent.prototype.sayHello = function() {
console.log('你好, 我是' + this.name);
};
function Child(name) {
Parent.call(this, name);
}
var parent = new Parent();
parent.sayHello() //你好, 我是爸爸
var child1 = new Child('小白');
child1.sayHello() // child1.sayHello is not a function
3. 组合继承(Combination Inheritance):
-
实现方式: 同时使用原型链继承和构造函数继承。
-
优点:
- 解决了原型链继承和构造函数继承各自的缺点。
- 既可以继承父类原型上的方法,又可以避免属性共享问题。
-
缺点:
- 子类原型链上存在两份相同的属性,存在内存浪费。
js
function Parent(name) {
this.name = name || '爸爸';
this.hobbies = ['吃饭','睡觉'];
}
Parent.prototype.getHobbies = function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};
function Child (name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
child1.getHobbies() //小白的爱好是吃饭和睡觉和哼小曲
var child2 = new Child('小布');
child2.getHobbies() //小布的爱好是吃饭和睡觉
融合原型链继承和构造函数的优点,是 JavaScript 中最常⽤
的继承模式。
4. 原型式继承(Prototype Pattern):
- 实现方式: 利用一个空的构造函数作为中介,创建一个对象并将该对象的原型指向某个对象。
- 优点: 简单方便,可以创建多个对象,共享同一个原型。
- 缺点:
- 无法传递参数。
- 引用类型的属性会被所有实例共享,存在属性共享的问题。
js
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
var parent = {
name: '爸爸',
hobbies: ['吃饭','睡觉'],
getHobbies: function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
}
};
var child1 = createObj(parent);
child1.name = '小白';
child1.hobbies.push('哼小曲');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲
var child2 = createObj(parent);
child1.name = '小布';
child1.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲
缺点:包含引⽤类型的属性值始终都会共享相应的值,这点跟原型链继承⼀样。
5. 寄生式继承(Parasitic Inheritance):
- 实现方式: 在原型式继承的基础上,对对象进行扩展,并返回扩展后的对象。
- 优点: 在不破坏原型链的情况下,对对象进行扩展。
- 缺点: 无法传递参数,存在属性共享的问题。
js
function createObj(original) {
var clone = Object.create(original);
clone.getHobbies = function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};
return clone;
}
var parent = {
name: '爸爸',
hobbies: ['吃饭','睡觉'],
};
var child1 = createObj(parent);
child1.name = '小白';
child1.hobbies.push('哼小曲');
child1.getHobbies(); // 小白的爱好是吃饭和睡觉和哼小曲
var child2 = createObj(parent);
child2.name = '小布';
child2.getHobbies(); // 小布的爱好是吃饭和睡觉和哼小曲
6. 寄生组合式继承(Parasitic Combination Inheritance):
- 实现方式: 使用寄生式继承来继承父类原型,然后将结果指定给子类的原型。
- 优点: 解决了组合继承中父类被调用两次构造函数的
js
// 父类构造函数
function Parent(name) {
this.name = name || '爸爸';
this.hobbies = ['吃饭','睡觉'];
}
// 父类原型方法
Parent.prototype.getHobbies = function() {
console.log(this.name + '的爱好是' + this.hobbies.join('和'));
};
// 子类构造函数
function Child (name) {
Parent.call(this, name);
}
// 使用寄生方式继承父类的原型方法
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
// 寄生组合式继承
inheritPrototype(Child, Parent);
// 实例化子类
var child1 = new Child('小白');
child1.hobbies.push('哼小曲');
child1.getHobbies(); //小白的爱好是吃饭和睡觉和哼小曲
var child2 = new Child('小布');
child2.getHobbies(); // 小布的爱好是吃饭和睡觉