继承是面向对象软件技术当中的一个概念, 与朵态、封装共为面向对象的三个基本特征. 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
原型链继承
通过将子类的原型对象指向父类的实例, 实现继承访问父类属性方法
// 定义父类
function Parent() {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
}
// 定义子类
function Child() {
this.name = "child";
}
Child.prototype = new Parent(); // 将子类的原型对象指向父类的实例
Child.prototype.constructor = Child; // 修复constructor使符合原型链规定
const child = new Child(); // 实例化子类
child.say(); // child
console.log(child instanceof Parent) // true 判断child的构造函数Child的prototype对象是否在Parent的原型链上
特点
- 父类新增原型方法与属性, 子类都能访问到
- 非常纯粹的继承关系, 实例是子类的实例, 也是父类的实例
- 子类实例可以继承父类构造属性和方法、父类原型属性和方法
不足
- 无法实现多继承
- 子类实例化时无法向父类的构造函数传参
- 所有子类实例都会共享父类的原型对象中的属性
构造函数继承
当子类构造函数被调用时, 借助call
或者apply
调用父类构造方法实现对于this
当拓展
function Parent(from) {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
this.from = from;
}
// 定义子类
function Child(from) {
Parent.call(this, from); // 调用父类构造函数并绑定this来拓展Child实例成员方法, 可以传递参数
this.name = "child";
}
const child = new Child("child");
child.say(); // child
console.log(child.from); // child
特点
- 子类实例不会共享父类属性方法
- 实例化子类时可以向父类构造函数传参
- 通过调用多个父类构造函数可以实现多继承
不足
- 实例并不是父类的实例, 只是子类的实例
- 只继承了父类的构造函数的属性和方法, 没有继承父类原型的属性和方法
- 每个子类都有父类实例函数的副本, 拷贝了父类函数而不是引用, 影响性能
实例继承
为父类实例增加成员与方法, 作为实例返回
// 定义父类
function Parent(from) {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
this.from = from;
}
// 定义子类
function Child(from) {
let instance = new Parent(from);
instance.name = "child";
return instance;
}
const child = new Child("child"); // 实例化子类
child.say(); // child
console.log(child.from); // child
特点
- 实例化子类时可以向父类构造函数传参
- 子类的实例化方法可以为
new Child()
或直接调用Child()
不足
- 不支持多继承
- 实例是父类的实例, 而不是子类的实例
- 同样也是将父类的成员与方法做了实例化拷贝
拷贝继承
通过直接将父类的属性拷贝到子类的原型中实现继承
// 定义父类
function Parent(from) {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
}
// 定义子类
function Child(from) {
let instance = new Parent(from);
for (let item in instance) {
Child.prototype[item] = instance[item];
this.name = "child";
}
}
const child = new Child("child"); // 实例化子类
child.say(); // child
console.log(child.from); // child
特点
- 支持多继承
- 实例化子类时可以向父类构造函数传参
不足
- 无法获取父类不可枚举的方法
- 同样也是将父类的成员与方法做了实例化并拷贝
原型式继承
通过共享原型对象实现继承
// 定义父类
function Parent() {}
Parent.prototype.name = "parent";
Parent.prototype.say = function() {
console.log(this.name);
}
// 定义子类
function Child(from) {
this.name = "child";
}
Child.prototype = Parent.prototype; // 共享原型
Child.prototype.constructor = Child;
const child = new Child("child"); // 实例化子类
child.say(); // child
特点
- 实现了方法与属性的复用
- 父类新增原型方法与属性, 子类都能访问到
不足
- 不能继承父类构造函数到实例对象到成员
- 所有子类实例都会共享父类的原型对象中的属性
组合继承
组合原型继承链和借用构造函数继承, 结合了两种模式的优点, 传参和复用
function Parent(from) {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
this.from = from;
}
// 定义子类
function Child(from) {
Parent.call(this, from);
this.name = "child";
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child("child"); // 实例化子类
child.say(); // child
console.log(child.from); // child
特点
- 原型方法可以复用
- 既是子类的实例, 也是父类的实例
- 实例化子类时可以向父类构造函数传参
- 可以继承实例属性和方法, 也可以继承原型属性和方法
不足
- 调用了两次父类构造函数, 生成了两份实例, 子类的构造函数的拷贝会代替原型上的父类构造函数的实例
寄生组合继承
通过寄生方式, 砍掉父类的实例属性, 在调用两次父类的构造函数的时候, 就不会初始化两次实例方法和属性, 避免了组合继承的缺点
function Parent(from) {
this.name = "parent";
this.say = function() {
console.log(this.name);
}
this.from = from;
}
// 定义子类
function Child(from) {
Parent.call(this, from);
this.name = "child";
}
const f = function() {}; // 创建一个没有实例方法的类
f.prototype = Parent.prototype; // 浅拷贝父类原型
Child.prototype = new f(); // 实例化f, 此时没有实例化方法调用, 同时将原型链建立
const child = new Child("child"); // 实例化子类
child.say(); // child
console.log(child.from); // child
特点
- 比较完善
不足
- 相对比较复杂