引言

JS系列暂定 27 篇,从根底,到原型,到异步,到设计模式,到架构模式等,

本篇是 JS系列中第 3 篇,文章主讲 JS 继承,包含原型链继承、构造函数继承、组合继承、寄生组合继承、原型式继承、 ES6 继承,以及 多继承与 new 。

ES5 继承

先定义一个父类

function SuperType () {  // 属性  this.name = 'SuperType';}// 原型办法SuperType.prototype.sayName = function() {  return this.name;};

一、 原型链继承

根本思维

将父类的实例作为子类的原型

// 父类function SuperType () {  this.name = 'SuperType'; // 父类属性}SuperType.prototype.sayName = function () { // 父类原型办法  return this.name;};// 子类function SubType () {  this.subName = "SubType"; // 子类属性};SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例// 这里实例化一个 SuperType 时, 实际上执行了两步// 1,新创建的对象复制了父类构造函数内的所有属性及办法// 2,并将原型 __proto__ 指向了父类的原型对象SubType.prototype.saySubName = function () { // 子类原型办法  return this.subName;}// 子类实例let instance = new SubType();// instanceof 通过判断对象的 prototype 链来确定对象是否是某个类的实例instance instanceof SubType; // trueinstance instanceof SuperType; // true// 留神SubType instanceof SuperType; // falseSubType.prototype instanceof SuperType ; // true

特点:

利用原型,让一个援用类型继承另一个援用类型的属性及办法

长处:

继承了父类的模板,又继承了父类的原型对象

毛病:

  • 能够在子类构造函数中,为子类实例减少实例属性。如果要新增原型属性和办法,则必须放在 SubType.prototype = new SuperType('SubType'); 这样的语句之后执行。
  • 无奈实现多继承
  • 来自原型对象的所有属性被所有实例共享
  • // 父类function SuperType () {  this.colors = ["red", "blue", "green"];  this.name = "SuperType";}// 子类function SubType () {}// 原型链继承SubType.prototype = new SuperType();// 实例1var instance1 = new SubType();instance1.colors.push("blcak");instance1.name = "change-super-type-name";console.log(instance1.colors); // ["red", "blue", "green", "blcak"]console.log(instance1.name); // change-super-type-name// 实例2var instance2 = new SubType();console.log(instance2.colors); // ["red", "blue", "green", "blcak"]console.log(instance2.name); // SuperType

    留神:更改 SuperType 援用类型属性时,会使 SubType 所有实例共享这一更新。根底类型属性更新则不会。

  • 创立子类实例时,无奈向父类构造函数传参,或者说是,没方法在不影响所有对象实例的状况下,向超类的构造函数传递参数

参考 前端进阶面试题具体解答

二、 结构继承

根本思维:

在子类型的构造函数外部调用父类型构造函数。

留神:

  • 函数只不过是在特定环境中执行代码的对象,所以这里应用 apply/call 来实现。
  • 应用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
// 父类function SuperType (name) {  this.name = name; // 父类属性}SuperType.prototype.sayName = function () { // 父类原型办法  return this.name;};// 子类function SubType () {  // 调用 SuperType 构造函数  SuperType.call(this, 'SuperType'); // 在子类构造函数中,向父类构造函数传参  // 为了保障子父类的构造函数不会重写子类的属性,须要在调用父类构造函数后,定义子类的属性  this.subName = "SubType"; // 子类属性};// 子类实例let instance = new SubType(); // 运行子类构造函数,并在子类构造函数中运行父类构造函数,this绑定到子类

长处:

解决了1中子类实例共享父类援用对象的问题,实现多继承,创立子类实例时,能够向父类传递参数

毛病:

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和办法,不能继承原型属性/办法
  • 无奈实现函数复用,每个子类都有父类实例函数的正本,影响性能

三. 组合继承

顾名思义,组合继承就是将原型链继承与构造函数继承组合在一起,从而施展两者之长的一种继承模式。

根本思维:

应用原型链继承应用对原型属性和办法的继承,通过构造函数继承来实现对实例属性的继承。这样既能通过在原型上定义方法实现函数复用,又能保障每个实例都有本人的属性。

通过调用父类结构,继承父类的属性并保留传参的长处,而后通过将父类实例作为子类原型,实现函数复用

// 父类function SuperType (name) {  this.colors = ["red", "blue", "green"];  this.name = name; // 父类属性}SuperType.prototype.sayName = function () { // 父类原型办法  return this.name;};// 子类function SubType (name, subName) {  // 调用 SuperType 构造函数  SuperType.call(this, name); // ----第二次调用 SuperType----  this.subName = subName;};// ----第一次调用 SuperType----SubType.prototype = new SuperType(); // 重写原型对象,代之以一个新类型的实例SubType.prototype.constructor = SubType; // 组合继承须要修复构造函数指向SubType.prototype.saySubName = function () { // 子类原型办法  return this.subName;}// 子类实例let instance = new SubType('An', 'sisterAn')instance.colors.push('black')console.log(instance.colors) // ["red", "blue", "green", "black"]instance.sayName() // Aninstance.saySubName() // sisterAnlet instance1 = new SubType('An1', 'sisterAn1')console.log(instance1.colors) //  ["red", "blue", "green"]instance1.sayName() // An1instance1.saySubName() // sisterAn1

第一次调用 SuperType 构造函数时,SubType.prototype 会失去两个属性namecolors;当调用 SubType 构造函数时,第二次调用 SuperType 构造函数,这一次又在新对象属性上创立了 namecolors,这两个属性就会屏蔽原型对象上的同名属性。

// instanceof:instance 的原型链是针对 SuperType.prototype 进行查看的instance instanceof SuperType // trueinstance instanceof SubType // true// isPrototypeOf:instance 的原型链是针对 SuperType 自身进行查看的SuperType.prototype.isPrototypeOf(instance) // trueSubType.prototype.isPrototypeOf(instance) // true

[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-72jtuatW-1677146968659)(https://p1-jj.byteimg.com/tos...)]

长处:

补救了形式2的缺点,能够继承实例属性/办法,也能够继承原型属性/办法,不存在援用属性共享问题,可传参,可复用

毛病:

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

四. 寄生组合继承

在组合继承中,调用了两次父类构造函数,这里 通过通过寄生形式,砍掉父类的实例属性,这样,在调用两次父类的结构的时候,就不会初始化两次实例办法/属性,防止的组合继承的毛病

次要思维:

借用 构造函数 继承 属性 ,通过 原型链的混成模式 来继承 办法

// 父类function SuperType (name) {  this.colors = ["red", "blue", "green"];  this.name = name; // 父类属性}SuperType.prototype.sayName = function () { // 父类原型办法  return this.name;};// 子类function SubType (name, subName) {  // 调用 SuperType 构造函数  SuperType.call(this, name); // ----第二次调用 SuperType,继承实例属性----  this.subName = subName;};// ----第一次调用 SuperType,继承原型属性----SubType.prototype = Object.create(SuperType.prototype)SubType.prototype.constructor = SubType; // 留神:加强对象let instance = new SubType('An', 'sisterAn')

长处:

  • 只调用一次 SuperType 构造函数,只创立一份父类属性
  • 原型链放弃不变
  • 可能失常应用 instanceofisPrototypeOf

五. 原型式继承

实现思路:

实现思路就是将子类的原型设置为父类的原型

// 父类function SuperType (name) {  this.colors = ["red", "blue", "green"];  this.name = name; // 父类属性}SuperType.prototype.sayName = function () { // 父类原型办法  return this.name;};/** 第一步 */// 子类,通过 call 继承父类的实例属性和办法,不能继承原型属性/办法function SubType (name, subName) {  SuperType.call(this, name); // 调用 SuperType 的构造函数,并向其传参   this.subName = subName;}/** 第二步 */// 解决 call 无奈继承父类原型属性/办法的问题// Object.create 办法承受传入一个作为新创建对象的原型的对象,创立一个领有指定原型和若干个指定属性的对象// 通过这种办法指定的任何属性都会笼罩原型对象上的同名属性SubType.prototype = Object.create(SuperType.prototype, {   constructor: { // 留神指定 SubType.prototype.constructor = SubType    value: SubType,    enumerable: false,    writable: true,    configurable: true  },  run : {     value: function(){ // override      SuperType.prototype.run.apply(this, arguments);           // call super          // ...    },    enumerable: true,    configurable: true,     writable: true  }}) /** 第三步 */// 最初:解决 SubType.prototype.constructor === SuperType 的问题// 这里,在上一步曾经指定,这里不须要再操作// SubType.prototype.constructor = SubType;var instance = new SubType('An', 'sistenAn')

多继承

如果心愿能 多继承 ,可应用 混入 的形式

// 父类 SuperTypefunction SuperType () {}// 父类 OtherSuperTypefunction OtherSuperType () {}// 多继承子类function AnotherType () {    SuperType.call(this) // 继承 SuperType 的实例属性和办法    OtherSuperType.call(this) // 继承 OtherSuperType 的实例属性和办法}// 继承一个类AnotherType.prototype = Object.create(SuperType.prototype);// 应用 Object.assign 混合其它Object.assign(AnotherType.prototype, OtherSuperType.prototype);// Object.assign 会把  OtherSuperType 原型上的函数拷贝到 AnotherType 原型上,使 AnotherType 的所有实例都可用 OtherSuperType 的办法// 从新指定 constructorAnotherType.prototype.constructor = AnotherType;AnotherType.prototype.myMethod = function() {     // do a thing};let instance = new AnotherType()

最重要的局部是:

  • SuperType.call 继承实例属性办法
  • Object.create() 来继承原型属性与办法
  • 批改 SubType.prototype.constructor的指向

ES6 继承

首先,实现一个简略的 ES6 继承:

class People {    constructor(name) {        this.name = name    }    run() { }}// extends 相当于办法的继承// 替换了下面的3行代码class Man extends People {    constructor(name) {        // super 相当于属性的继承        // 替换了 People.call(this, name)        super(name)        this.gender = '男'    }    fight() { }}

外围代码

extends 继承的外围代码如下,其实现和上述的寄生组合式继承形式一样

function _inherits(subType, superType) {    // 创建对象,Object.create 创立父类原型的一个正本    // 加强对象,补救因重写原型而失去的默认的 constructor 属性    // 指定对象,将新创建的对象赋值给子类的原型 subType.prototype    subType.prototype = Object.create(superType && superType.prototype, {        constructor: { // 重写 constructor            value: subType,            enumerable: false,            writable: true,            configurable: true        }    });    if (superType) {        Object.setPrototypeOf             ? Object.setPrototypeOf(subType, superType)             : subType.__proto__ = superType;    }}

继承的应用场景

  • 不要仅仅为了应用而应用它们,这只是在浪费时间而已。
  • 当须要创立 一系列领有类似个性的对象 时,那么创立一个蕴含所有共有性能的通用对象,而后在更非凡的对象类型中继承这些个性。
  • 应防止多继承,造成凌乱。

注: 思考到JavaScript的工作形式,因为原型链等个性的存在,在不同对象之间性能的共享通常被叫做 委托 - 非凡的对象将性能委托给通用的对象类型实现。这兴许比将其称之为继承更为贴切,因为“被继承”了的性能并没有被拷贝到正在“进行继承”的对象中,相同它仍存在于通用的对象中。