继承是面向对象思想的组成部分之一,通过继承可以实现代码复用,减少代码量。继承主要有两个讨论对象:父对象与子对象,子对象继承父对象的属性与方法,之后子对象就可以像使用自己的方法与属性一样使用父对象的属性与方法。在开始正式介绍对象之前,先写好父对象与子对象的构造函数:
function Parent(name) {
this.name = name;
}
Parent.prototype.work = function () {
console.log(`${this.name} working...`);
};
function Child(age) {
this.age = age;
}
Child.prototype.eat = function () {
console.log(`${this.age}eating...`);
};
如下图所示:
首字母大写的函数是构造函数,用蓝色背景表示,构造函数的自带的原型对象放在它的上面,用绿色表示。每个构造函数的实例放在构造函数下面,用土色表示。
原型继承
将子类的原型设置为父类的示例:
function Parent(name) {
this.name = name;
}
Parent.prototype.work = function () {
console.log(`${this.name} working...`);
};
// 原型继承
Child.prototype = new Parent("Tom");
Child.prototype.sleep = function () {
console.log(`${this.age} sleeping...`);
};
const c = new Child(9);
//c.eat(); // 失去原来的原型对象
c.work(); // 获得父对象方法
c.sleep();
图解:
优点
简单易于实现,父类的新增的方法与属性子类都能访问。
缺点
1)可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数的后面
2)创建子类实例时,不能向父类构造函数中传参数。
借用构造函数继承
借用继承是指在子类的构造函数中调用父类的构造函数:
function Parent(name) {
this.name = name;
}
Parent.prototype.work = function () {
console.log(`${this.name} working...`);
};
function Child(name, age) {
Parent.call(this, name); // 借用继承
this.age = age;
}
图解:
优点
只需要继承父类的属性时这种方式很简单。
缺点
只能继承父类自己的属性,不能继承父类方法,父类原型上的属性与方法也不能继承。
组合继承
一个完美的继承应该是:公用的方法或属性在原型上,特有的自己保存。
原型继承虽然可以保证子类访问到父类的属性或方法,但是父类的某些属性保存在了子类的原型上,没有保存在自己的示例对象上面,例如:当个人都有自己的 name 属性时,需要将 name 属性添加到示例对象上,而不是原型上面,通过组合原型继承与借用继承实现:
function Parent(name) {
this.name = name;
}
Parent.prototype.work = function () {
console.log(`${this.name} working...`);
};
function Child(name, age) {
Parent.call(this, name); // 借用继承
this.age = age;
}
Child.prototype = new Parent("Tom");
Child.prototype.constructor = Child;
Child.prototype.eat = function () {
console.log(`${this.age}eating...`);
};
const c = new Child("Tom", 12);
图解:
优点
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点。而且,使用 instanceof 操作符和isPrototype()方法也能够用于识别基于组合继承创建的对象。
缺点
会调用两次父类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
原型式继承
基于当前已有对象创建新对象:
function createAnother(p) {
function Child() {};
Child.prototype = p;
return new Child();
}
var p = {
name: "父对象",
say: function () {},
};
var c = createAnother(p);
console.log(c.name); // "父对象"
图解:
使用简写 ES5 Object.create()
简写:
var c = Object.create(p);
原型式继承与原型继承很像,对一个中介对象使用原型继承,看起来像是拷贝了一个对象,但实际上是把它的原型变成父对象。实际上就是:c.__proto__=p
。为什么不直接这么干呢?
寄生继承
寄生继承就是通过创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象:
function createChild(parent) {
const child = Object.create(parent); // 通过 Object.create() 函数创建一个新对象
// 增强这个对象
child.sayGood = function () {
alert("hello world!!!");
};
return child; // 返回这个对象
}
寄生继承看起来不像严格意义上的继承。
寄生组合式继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。
function setPrototypeChain(Child, Parent) {
const c_prototype = Object.create(Parent.prototype);
c_prototype.constructor = Child;
Child.prototype = c_prototype;
}
// 相当于
// Child.prototype.__proto__ = Parent.prototype
图解:
ES6 class 继承
ES6 使用 extends 与 super 实现继承:
class Parent {
constructor(name) {
this.name = name;
}
work() {
console.log(`${this.name} working...`);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
eat() {
console.log(`${this.name} at ${this.age} is eating...`);
}
}
const c = new Child("tom", 12);
c.eat();
c.work();
图解:
思考:ES6 实现的继承方式是哪种?
最后插个眼
♥ 我是前端工程师:你的甜心森。非常感谢大家的点赞与关注,欢迎大家参与讨论或协作。
★ 本文开源,采用 CC BY-SA 4.0 协议,转载请注明出处:前端工程师的自我修养. GitHub.com@xiayulu.
★ 创作合作或招聘信息请发私信或邮件:zuiaiqiansen@163.com,注明主题:创作合作或招聘前端工程师。
ayulu/FrontEndCultivation),采用 CC BY-SA 4.0 协议,转载请注明出处:前端工程师的自我修养. GitHub.com@xiayulu.★ 创作合作或招聘信息请发私信或邮件:zuiaiqiansen@163.com,注明主题:创作合作或招聘前端工程师。